package core import ( "fmt" "strings" "github.com/dosco/super-graph/core/internal/psql" "github.com/dosco/super-graph/core/internal/qcode" "github.com/gobuffalo/flect" ) func (sg *SuperGraph) initConfig() error { c := sg.conf for k, v := range c.Inflections { flect.AddPlural(k, v) } // Tables: Validate and sanitize tm := make(map[string]struct{}) for i := 0; i < len(c.Tables); i++ { t := &c.Tables[i] // t.Name = flect.Pluralize(strings.ToLower(t.Name)) if _, ok := tm[t.Name]; ok { sg.conf.Tables = append(c.Tables[:i], c.Tables[i+1:]...) sg.log.Printf("WRN duplicate table found: %s", t.Name) } tm[t.Name] = struct{}{} t.Table = flect.Pluralize(strings.ToLower(t.Table)) } sg.roles = make(map[string]*Role) for i := 0; i < len(c.Roles); i++ { role := &c.Roles[i] role.Name = sanitize(role.Name) if _, ok := sg.roles[role.Name]; ok { c.Roles = append(c.Roles[:i], c.Roles[i+1:]...) sg.log.Printf("WRN duplicate role found: %s", role.Name) } role.Match = sanitize(role.Match) role.tm = make(map[string]*RoleTable) for n, table := range role.Tables { role.tm[table.Name] = &role.Tables[n] } sg.roles[role.Name] = role } // If user role not defined then create it if _, ok := sg.roles["user"]; !ok { ur := Role{ Name: "user", tm: make(map[string]*RoleTable), } c.Roles = append(c.Roles, ur) sg.roles["user"] = &ur } // If anon role is not defined then create it if _, ok := sg.roles["anon"]; !ok { ur := Role{ Name: "anon", tm: make(map[string]*RoleTable), } c.Roles = append(c.Roles, ur) sg.roles["anon"] = &ur } if c.RolesQuery == "" { sg.log.Printf("INF roles_query not defined: attribute based access control disabled") } else { n := 0 for k, v := range sg.roles { if k == "user" || k == "anon" { n++ } else if v.Match != "" { n++ } } sg.abacEnabled = (n > 2) if !sg.abacEnabled { sg.log.Printf("WRN attribute based access control disabled: no custom roles found (with 'match' defined)") } } return nil } func getDBTableAliases(c *Config) map[string][]string { m := make(map[string][]string, len(c.Tables)) for i := range c.Tables { t := c.Tables[i] if t.Table != "" && t.Type == "" { m[t.Table] = append(m[t.Table], t.Name) } } return m } func addTables(c *Config, di *psql.DBInfo) error { var err error for _, t := range c.Tables { switch t.Type { case "json", "jsonb": err = addJsonTable(di, t.Columns, t) case "polymorphic": err = addVirtualTable(di, t.Columns, t) } if err != nil { return err } } return nil } func addJsonTable(di *psql.DBInfo, cols []Column, t Table) error { // This is for jsonb columns that want to be tables. bc, ok := di.GetColumn(t.Table, t.Name) if !ok { return fmt.Errorf( "json table: column '%s' not found on table '%s'", t.Name, t.Table) } if bc.Type != "json" && bc.Type != "jsonb" { return fmt.Errorf( "json table: column '%s' in table '%s' is of type '%s'. Only JSON or JSONB is valid", t.Name, t.Table, bc.Type) } table := psql.DBTable{ Name: t.Name, Key: strings.ToLower(t.Name), Type: bc.Type, } columns := make([]psql.DBColumn, 0, len(cols)) for i := range cols { c := cols[i] columns = append(columns, psql.DBColumn{ Name: c.Name, Key: strings.ToLower(c.Name), Type: c.Type, }) } di.AddTable(table, columns) bc.FKeyTable = t.Name return nil } func addVirtualTable(di *psql.DBInfo, cols []Column, t Table) error { if len(cols) == 0 { return fmt.Errorf("polymorphic table: no id column specified") } c := cols[0] if c.ForeignKey == "" { return fmt.Errorf("polymorphic table: no 'related_to' specified on id column") } s := strings.SplitN(c.ForeignKey, ".", 2) if len(s) != 2 { return fmt.Errorf("polymorphic table: foreign key must be .") } di.VTables = append(di.VTables, psql.VirtualTable{ Name: t.Name, IDColumn: c.Name, TypeColumn: s[0], FKeyColumn: s[1], }) return nil } func addForeignKeys(c *Config, di *psql.DBInfo) error { for _, t := range c.Tables { if t.Type != "" { continue } for _, c := range t.Columns { if c.ForeignKey == "" { continue } if err := addForeignKey(di, c, t); err != nil { return err } } } return nil } func addForeignKey(di *psql.DBInfo, c Column, t Table) error { var tn string if t.Type == "polymorphic" { tn = t.Table } else { tn = t.Name } c1, ok := di.GetColumn(tn, c.Name) if !ok { return fmt.Errorf( "config: invalid table '%s' or column '%s' defined", tn, c.Name) } v := strings.SplitN(c.ForeignKey, ".", 2) if len(v) != 2 { return fmt.Errorf( "config: invalid foreign_key defined for table '%s' and column '%s': %s", tn, c.Name, c.ForeignKey) } // check if it's a polymorphic foreign key if _, ok := di.GetColumn(tn, v[0]); ok { c2, ok := di.GetColumn(tn, v[1]) if !ok { return fmt.Errorf( "config: invalid column '%s' for polymorphic relationship on table '%s' and column '%s'", v[1], tn, c.Name) } c1.FKeyTable = v[0] c1.FKeyColID = []int16{c2.ID} return nil } fkt, fkc := v[0], v[1] c3, ok := di.GetColumn(fkt, fkc) if !ok { return fmt.Errorf( "config: foreign_key for table '%s' and column '%s' points to unknown table '%s' and column '%s'", t.Name, c.Name, v[0], v[1]) } c1.FKeyTable = fkt c1.FKeyColID = []int16{c3.ID} return nil } 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.DefaultBlock); err != nil { return err } } } return nil } func addRole(qc *qcode.Compiler, r Role, t RoleTable, defaultBlock bool) error { ro := false // read-only if defaultBlock && r.Name == "anon" { ro = true } if t.ReadOnly { ro = true } 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, } } if t.Insert != nil { insert = qcode.InsertConfig{ Filters: t.Insert.Filters, Columns: t.Insert.Columns, Presets: t.Insert.Presets, Block: t.Insert.Block, } } if t.Update != nil { update = qcode.UpdateConfig{ Filters: t.Update.Filters, Columns: t.Update.Columns, Presets: t.Update.Presets, Block: t.Update.Block, } } 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{ Query: query, Insert: insert, Update: update, Delete: del, }) } func (r *Role) GetTable(name string) *RoleTable { return r.tm[name] } func sanitize(value string) string { return strings.ToLower(strings.TrimSpace(value)) }