BREAKING CHANGE: super-graph/core now defaults to allow all in anon role

This commit is contained in:
Vikram Rangnekar
2020-05-28 00:07:01 -04:00
parent 2241364d00
commit 1fb7f0e6c8
15 changed files with 465 additions and 291 deletions

View File

@ -30,12 +30,10 @@ type Config struct {
// or other database functions
SetUserID bool `mapstructure:"set_user_id"`
// DefaultAllow reverses the blocked by default behaviour for queries in
// anonymous mode. (anon role)
// For example if the table `users` is not listed under the anon role then
// access to it would by default for unauthenticated queries this reverses
// this behavior (!!! Use with caution !!!!)
DefaultAllow bool `mapstructure:"default_allow"`
// 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)
@ -57,6 +55,9 @@ type Config struct {
// 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
@ -108,12 +109,12 @@ type Role struct {
// RoleTable struct contains role specific access control values for a database table
type RoleTable struct {
Name string
ReadOnly *bool `mapstructure:"read_only"`
ReadOnly bool `mapstructure:"read_only"`
Query Query
Insert Insert
Update Update
Delete Delete
Query *Query
Insert *Insert
Update *Update
Delete *Delete
}
// Query struct contains access control values for query operations
@ -122,7 +123,7 @@ type Query struct {
Filters []string
Columns []string
DisableFunctions bool `mapstructure:"disable_functions"`
Block *bool
Block bool
}
// Insert struct contains access control values for insert operations
@ -130,7 +131,7 @@ type Insert struct {
Filters []string
Columns []string
Presets map[string]string
Block *bool
Block bool
}
// Insert struct contains access control values for update operations
@ -138,14 +139,59 @@ type Update struct {
Filters []string
Columns []string
Presets map[string]string
Block *bool
Block bool
}
// Delete struct contains access control values for delete operations
type Delete struct {
Filters []string
Columns []string
Block *bool
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

View File

@ -90,7 +90,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

View File

@ -196,7 +196,7 @@ func addForeignKey(di *psql.DBInfo, c Column, t Table) error {
func addRoles(c *Config, qc *qcode.Compiler) error {
for _, r := range c.Roles {
for _, t := range r.Tables {
if err := addRole(qc, r, t, c.DefaultAllow); err != nil {
if err := addRole(qc, r, t, c.DefaultBlock); err != nil {
return err
}
}
@ -205,67 +205,56 @@ func addRoles(c *Config, qc *qcode.Compiler) error {
return nil
}
func addRole(qc *qcode.Compiler, r Role, t RoleTable, defaultAllow bool) error {
ro := true // read-only
func addRole(qc *qcode.Compiler, r Role, t RoleTable, defaultBlock bool) error {
ro := false // read-only
if defaultAllow {
ro = false
if defaultBlock && r.Name == "anon" {
ro = true
}
if r.Name != "anon" {
ro = false
if t.ReadOnly {
ro = true
}
if t.ReadOnly != nil {
ro = *t.ReadOnly
query := qcode.QueryConfig{Block: false}
insert := qcode.InsertConfig{Block: ro}
update := qcode.UpdateConfig{Block: ro}
del := qcode.DeleteConfig{Block: ro}
if t.Query != nil {
query = qcode.QueryConfig{
Limit: t.Query.Limit,
Filters: t.Query.Filters,
Columns: t.Query.Columns,
DisableFunctions: t.Query.DisableFunctions,
Block: t.Query.Block,
}
}
blocked := struct {
query bool
insert bool
update bool
delete bool
}{false, ro, ro, ro}
if t.Query.Block != nil {
blocked.query = *t.Query.Block
}
if t.Insert.Block != nil {
blocked.insert = *t.Insert.Block
}
if t.Update.Block != nil {
blocked.update = *t.Update.Block
}
if t.Delete.Block != nil {
blocked.delete = *t.Delete.Block
if t.Insert != nil {
insert = qcode.InsertConfig{
Filters: t.Insert.Filters,
Columns: t.Insert.Columns,
Presets: t.Insert.Presets,
Block: t.Insert.Block,
}
}
query := qcode.QueryConfig{
Limit: t.Query.Limit,
Filters: t.Query.Filters,
Columns: t.Query.Columns,
DisableFunctions: t.Query.DisableFunctions,
Block: blocked.query,
if t.Update != nil {
update = qcode.UpdateConfig{
Filters: t.Update.Filters,
Columns: t.Update.Columns,
Presets: t.Update.Presets,
Block: t.Update.Block,
}
}
insert := qcode.InsertConfig{
Filters: t.Insert.Filters,
Columns: t.Insert.Columns,
Presets: t.Insert.Presets,
Block: blocked.insert,
}
update := qcode.UpdateConfig{
Filters: t.Update.Filters,
Columns: t.Update.Columns,
Presets: t.Update.Presets,
Block: blocked.update,
}
del := qcode.DeleteConfig{
Filters: t.Delete.Filters,
Columns: t.Delete.Columns,
Block: blocked.delete,
if t.Delete != nil {
del = qcode.DeleteConfig{
Filters: t.Delete.Filters,
Columns: t.Delete.Columns,
Block: t.Delete.Block,
}
}
return qc.AddRole(r.Name, t.Name, qcode.TRConfig{

View File

@ -55,19 +55,6 @@ func TestSuperGraph(t *testing.T, db *sql.DB, before func(t *testing.T)) {
config.AllowListFile = "./allow.list"
config.RolesQuery = `SELECT * FROM users WHERE id = $user_id`
blockFalse := false
config.Roles = []core.Role{
core.Role{
Name: "anon",
Tables: []core.RoleTable{
core.RoleTable{Name: "users", ReadOnly: &blockFalse, Query: core.Query{Limit: 100}},
core.RoleTable{Name: "product", ReadOnly: &blockFalse, Query: core.Query{Limit: 100}},
core.RoleTable{Name: "line_item", ReadOnly: &blockFalse, Query: core.Query{Limit: 100}},
},
},
}
sg, err := core.NewSuperGraph(&config, db)
require.NoError(t, err)
ctx := context.Background()

View File

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

View File

@ -172,6 +172,8 @@ const (
type Compiler struct {
tr map[string]map[string]*trval
bl map[string]struct{}
defBlock bool
}
var expPool = sync.Pool{
@ -179,7 +181,7 @@ var expPool = sync.Pool{
}
func NewCompiler(c Config) (*Compiler, error) {
co := &Compiler{}
co := &Compiler{defBlock: c.DefaultBlock}
co.tr = make(map[string]map[string]*trval)
co.bl = make(map[string]struct{}, len(c.Blocklist))
@ -358,7 +360,7 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
}
} else if role == "anon" {
skipRender = true
skipRender = com.defBlock
}
selects = append(selects, Select{