diff --git a/go.mod b/go.mod index 80e78d944461b0f8dc9c3d4ef19a6792589bba70..69763ef6b473d4838d74e781fb40d99dee269759 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module gfx.cafe/gfx/pggat go 1.19 require ( - gfx.cafe/ghalliday1/pg3p v0.0.13 + gfx.cafe/ghalliday1/pg3p v0.0.15 gfx.cafe/ghalliday1/pgparser v0.0.9 gfx.cafe/util/go/bufpool v0.0.0-20220906091724-3a24b7f40ccf gfx.cafe/util/go/generic v0.0.0-20220917152604-80373e5a2c51 diff --git a/go.sum b/go.sum index 5f6824825855d4d830ec7f61e4fa826b736afeeb..bb086c90bd8ebc6d3dc835d2ba5f96a97e22dde5 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -gfx.cafe/ghalliday1/pg3p v0.0.13 h1:W5lk6ipc2FmVcjYtIev1W9U9Ha4SFl+qD340NuLe2Qo= -gfx.cafe/ghalliday1/pg3p v0.0.13/go.mod h1:dVy94HSyywugiaUJm+4EyhC41TOL3SHSr+FUubKa7s8= +gfx.cafe/ghalliday1/pg3p v0.0.15 h1:6n4wGfxcFHrFWiCurj0N2wC+/CV+AuVxfRTKmKW8HGQ= +gfx.cafe/ghalliday1/pg3p v0.0.15/go.mod h1:dVy94HSyywugiaUJm+4EyhC41TOL3SHSr+FUubKa7s8= gfx.cafe/ghalliday1/pgparser v0.0.9 h1:mj1819zaEO964rFMr9lzGIoKLdZdjWAs6CqPvcSAhYg= gfx.cafe/ghalliday1/pgparser v0.0.9/go.mod h1:mte3a6XnIWVexcvJJrkYQ2ozbSx/soIJ4ivXf8pS1No= gfx.cafe/util/go/bufpool v0.0.0-20220906091724-3a24b7f40ccf h1:ya4IK1D+Kq0DrFdrrZ7tjmp3BgoO4v5sCAeUytR6j1U= diff --git a/lib/config/config.go b/lib/config/config.go index 3bffbd6d088d0a51ee1f2a1b5405404270f80d40..a2ffd865f24529b71a3c9a98476b6aa3a14b6823 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -33,6 +33,19 @@ const ( USERROLE_READER UserRole = "reader" ) +func (r UserRole) CanUse(which ServerRole) bool { + switch r { + case USERROLE_ADMIN: + return true + case USERROLE_WRITER: + return true + case USERROLE_READER: + return which != SERVERROLE_PRIMARY + default: + return false + } +} + func Load(path string) (*Global, error) { var g Global ext := filepath.Ext(path) diff --git a/lib/gat/database/query_router/query_router.go b/lib/gat/database/query_router/query_router.go index 422568900c54ad96318247f2793dfa18359dc317..0e1a6e366e3383675b62873f0893d88dd97b95d6 100644 --- a/lib/gat/database/query_router/query_router.go +++ b/lib/gat/database/query_router/query_router.go @@ -5,6 +5,8 @@ import ( "gfx.cafe/gfx/pggat/lib/config" "gfx.cafe/gfx/pggat/lib/gat" "gfx.cafe/gfx/pggat/lib/util/cmux" + "gfx.cafe/ghalliday1/pg3p" + "gfx.cafe/ghalliday1/pg3p/lex" "strconv" "unicode" "unicode/utf8" @@ -12,6 +14,7 @@ import ( type QueryRouter struct { router cmux.Mux[gat.Client, error] + parser *pg3p.Parser } var DefaultRouter = func() *QueryRouter { @@ -64,43 +67,24 @@ var DefaultRouter = func() *QueryRouter { }) return &QueryRouter{ router: r, + parser: pg3p.NewParser(), } }() // Try to infer the server role to try to connect to // based on the contents of the query. // note that the user needs to be checked to see if they are allowed to access. -// TODO: implement func (r *QueryRouter) InferRole(query string) (config.ServerRole, error) { - var active_role config.ServerRole - // by default it will hit a primary (for now) - active_role = config.SERVERROLE_PRIMARY - //// ok now parse the query - //wk := &walk.AstWalker{ - // Fn: func(ctx, node any) (stop bool) { - // switch n := node.(type) { - // case *tree.Update, *tree.UpdateExpr, - // *tree.BeginTransaction, *tree.CommitTransaction, *tree.RollbackTransaction, - // *tree.SetTransaction, *tree.ShowTransactionStatus, *tree.Delete, *tree.Insert: - // // - // active_role = config.SERVERROLE_PRIMARY - // return true - // default: - // _ = n - // } - // return false - // }, - //} - //stmts, err := parser.Parse(query) - //if err != nil { - // log.Println("failed to parse (%query), assuming primary required", err) - // return config.SERVERROLE_PRIMARY, nil - //} - //_, err = wk.Walk(stmts, nil) - //if err != nil { - // return config.SERVERROLE_PRIMARY, err - //} - return active_role, nil + // parse the query + tokens := r.parser.Lex(query) + var role = config.SERVERROLE_REPLICA + for _, token := range tokens { + switch token.Token { + case lex.KeywordUpdate, lex.KeywordDelete, lex.KeywordInsert: + role = config.SERVERROLE_PRIMARY + } + } + return role, nil } func (r *QueryRouter) TryHandle(client gat.Client, query string) (handled bool, err error) { diff --git a/lib/gat/pool/transaction/worker.go b/lib/gat/pool/transaction/worker.go index 082e5a8806bfbf9517246aa81a068ada321225e5..4df00b13423e4d20946bc751cfdc1a04ca0ec07c 100644 --- a/lib/gat/pool/transaction/worker.go +++ b/lib/gat/pool/transaction/worker.go @@ -2,6 +2,7 @@ package transaction import ( "context" + "errors" "fmt" "gfx.cafe/gfx/pggat/lib/config" "gfx.cafe/gfx/pggat/lib/gat" @@ -229,6 +230,9 @@ func (w *worker) z_actually_do_describe(ctx context.Context, client gat.Client, // describe the portal // we can use a replica because we are just describing what this query will return, query content doesn't matter // because nothing is actually executed yet + if !w.w.user.Role.CanUse(config.SERVERROLE_REPLICA) { + return errors.New("permission denied") + } target := srv.Choose(config.SERVERROLE_REPLICA) if target == nil { return fmt.Errorf("describe('%+v') fail: no server", payload) @@ -266,6 +270,9 @@ func (w *worker) z_actually_do_execute(ctx context.Context, client gat.Client, p if err != nil { return err } + if !w.w.user.Role.CanUse(which) { + return errors.New("permission denied") + } target := srv.Choose(which) w.setCurrentBinding(client, target) defer w.unsetCurrentBinding(client, target) @@ -280,6 +287,9 @@ func (w *worker) z_actually_do_fn(ctx context.Context, client gat.Client, payloa return fmt.Errorf("fn('%+v') fail: no server", payload) } // call the function + if !w.w.user.Role.CanUse(config.SERVERROLE_PRIMARY) { + return errors.New("permission denied") + } target := srv.GetPrimary() if target == nil { return fmt.Errorf("fn('%+v') fail: no target ", payload) @@ -303,6 +313,9 @@ func (w *worker) z_actually_do_simple_query(ctx context.Context, client gat.Clie if err != nil { return fmt.Errorf("error parsing '%s': %w", payload, err) } + if !w.w.user.Role.CanUse(which) { + return errors.New("permission denied") + } // configures the server to run with a specific role target := srv.Choose(which) if target == nil { @@ -328,6 +341,9 @@ func (w *worker) z_actually_do_transaction(ctx context.Context, client gat.Clien if err != nil { return fmt.Errorf("error parsing '%s': %w", payload, err) } + if !w.w.user.Role.CanUse(which) { + return errors.New("permission denied") + } // configures the server to run with a specific role target := srv.Choose(which) if target == nil {