From c797deb4d069142914dd29daeb96421f32e2891c Mon Sep 17 00:00:00 2001 From: Vikram Rangnekar Date: Tue, 15 Oct 2019 02:30:19 -0400 Subject: [PATCH] Add built in 'anon' and 'user' roles --- config/allow.list | 202 +++++++++++++++++------ config/dev.yml | 4 +- examples/rails-app/Gemfile.lock | 273 ++++++++++++++++++++++++++++++++ psql/query.go | 18 ++- qcode/qcode.go | 15 +- serv/cmd.go | 5 +- serv/cmd_seed.go | 2 +- serv/core.go | 16 +- serv/http.go | 37 +---- serv/introsp.go | 36 +++++ serv/prepare.go | 2 +- serv/sqllog.go | 45 ++++++ 12 files changed, 553 insertions(+), 102 deletions(-) create mode 100644 serv/introsp.go create mode 100644 serv/sqllog.go diff --git a/config/allow.list b/config/allow.list index a17a562..196e46b 100644 --- a/config/allow.list +++ b/config/allow.list @@ -1,5 +1,27 @@ # http://localhost:8080/ +variables { + "data": [ + { + "name": "Protect Ya Neck", + "created_at": "now", + "updated_at": "now" + }, + { + "name": "Enter the Wu-Tang", + "created_at": "now", + "updated_at": "now" + } + ] +} + +mutation { + products(insert: $data) { + id + name + } +} + variables { "update": { "name": "Wu-Tang", @@ -16,16 +38,16 @@ mutation { } } -variables { - "data": { - "product_id": 5 - } -} - -mutation { - products(id: $product_id, delete: true) { +query { + users { id - name + email + picture: avatar + products(limit: 2, where: {price: {gt: 10}}) { + id + name + description + } } } @@ -73,6 +95,118 @@ query { } } +variables { + "data": [ + { + "name": "Gumbo1", + "created_at": "now", + "updated_at": "now" + }, + { + "name": "Gumbo2", + "created_at": "now", + "updated_at": "now" + } + ] +} + +query { + products { + id + name + user { + email + } + } +} + +variables { + "data": { + "product_id": 5 + } +} + +mutation { + products(id: $product_id, delete: true) { + id + name + } +} + +variables { + "data": [ + { + "name": "Gumbo1", + "created_at": "now", + "updated_at": "now" + }, + { + "name": "Gumbo2", + "created_at": "now", + "updated_at": "now" + } + ] +} + +query { + products { + id + name + price + users { + email + } + } +} + + +variables { + "data": { + "email": "gfk@myspace.com", + "full_name": "Ghostface Killah", + "created_at": "now", + "updated_at": "now" + } +} + +mutation { + user(insert: $data) { + id + } +} + +variables { + "data": [ + { + "name": "Gumbo1", + "created_at": "now", + "updated_at": "now" + }, + { + "name": "Gumbo2", + "created_at": "now", + "updated_at": "now" + } + ] +} + +query { + products { + id + name + users { + email + } + } +} + +query { + me { + id + email + full_name + } +} variables { "update": { @@ -112,62 +246,30 @@ query { } } - -query { - me { - id - email - full_name - } -} - -variables { - "data": { - "email": "gfk@myspace.com", - "full_name": "Ghostface Killah", - "created_at": "now", - "updated_at": "now" - } -} - -mutation { - user(insert: $data) { - id - } -} - -query { - users { - id - email - picture: avatar - products(limit: 2, where: {price: {gt: 10}}) { - id - name - description - } - } -} - variables { "data": [ { - "name": "Protect Ya Neck", + "name": "Gumbo1", "created_at": "now", "updated_at": "now" }, { - "name": "Enter the Wu-Tang", + "name": "Gumbo2", "created_at": "now", "updated_at": "now" } ] } -mutation { - products(insert: $data) { +query { + products { id name + description + users { + email + } } } + diff --git a/config/dev.yml b/config/dev.yml index a314dcd..c94e4bb 100644 --- a/config/dev.yml +++ b/config/dev.yml @@ -193,5 +193,5 @@ roles: tables: - name: users - select: - filter: ["{ account_id: { _eq: $account_id } }"] + select: + filter: ["{ account_id: { _eq: $account_id } }"] diff --git a/examples/rails-app/Gemfile.lock b/examples/rails-app/Gemfile.lock index e69de29..7a6e370 100644 --- a/examples/rails-app/Gemfile.lock +++ b/examples/rails-app/Gemfile.lock @@ -0,0 +1,273 @@ +GIT + remote: https://github.com/stympy/faker.git + revision: 4e9144825fcc9ba5c83cc0fd037779ab82f3120b + branch: master + specs: + faker (2.6.0) + i18n (>= 1.6, < 1.8) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (6.0.0) + actionpack (= 6.0.0) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailbox (6.0.0) + actionpack (= 6.0.0) + activejob (= 6.0.0) + activerecord (= 6.0.0) + activestorage (= 6.0.0) + activesupport (= 6.0.0) + mail (>= 2.7.1) + actionmailer (6.0.0) + actionpack (= 6.0.0) + actionview (= 6.0.0) + activejob (= 6.0.0) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (6.0.0) + actionview (= 6.0.0) + activesupport (= 6.0.0) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (6.0.0) + actionpack (= 6.0.0) + activerecord (= 6.0.0) + activestorage (= 6.0.0) + activesupport (= 6.0.0) + nokogiri (>= 1.8.5) + actionview (6.0.0) + activesupport (= 6.0.0) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (6.0.0) + activesupport (= 6.0.0) + globalid (>= 0.3.6) + activemodel (6.0.0) + activesupport (= 6.0.0) + activerecord (6.0.0) + activemodel (= 6.0.0) + activesupport (= 6.0.0) + activestorage (6.0.0) + actionpack (= 6.0.0) + activejob (= 6.0.0) + activerecord (= 6.0.0) + marcel (~> 0.3.1) + activesupport (6.0.0) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.1, >= 2.1.8) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + archive-zip (0.12.0) + io-like (~> 0.3.0) + bcrypt (3.1.13) + bindex (0.8.1) + bootsnap (1.4.5) + msgpack (~> 1.0) + builder (3.2.3) + byebug (11.0.1) + capybara (3.29.0) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (~> 1.5) + xpath (~> 3.2) + childprocess (3.0.0) + chromedriver-helper (2.1.1) + archive-zip (~> 0.10) + nokogiri (~> 1.8) + coffee-rails (4.2.2) + coffee-script (>= 2.2.0) + railties (>= 4.0.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + concurrent-ruby (1.1.5) + crass (1.0.4) + devise (4.7.1) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) + erubi (1.9.0) + execjs (2.7.0) + ffi (1.11.1) + globalid (0.4.2) + activesupport (>= 4.2.0) + i18n (1.7.0) + concurrent-ruby (~> 1.0) + io-like (0.3.0) + jbuilder (2.9.1) + activesupport (>= 4.2.0) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.3.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (0.9.2) + mimemagic (0.3.3) + mini_mime (1.0.2) + mini_portile2 (2.4.0) + minitest (5.12.2) + msgpack (1.3.1) + nio4r (2.5.2) + nokogiri (1.10.4) + mini_portile2 (~> 2.4.0) + orm_adapter (0.5.0) + pg (1.1.4) + public_suffix (4.0.1) + puma (3.12.1) + rack (2.0.7) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (6.0.0) + actioncable (= 6.0.0) + actionmailbox (= 6.0.0) + actionmailer (= 6.0.0) + actionpack (= 6.0.0) + actiontext (= 6.0.0) + actionview (= 6.0.0) + activejob (= 6.0.0) + activemodel (= 6.0.0) + activerecord (= 6.0.0) + activestorage (= 6.0.0) + activesupport (= 6.0.0) + bundler (>= 1.3.0) + railties (= 6.0.0) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (6.0.0) + actionpack (= 6.0.0) + activesupport (= 6.0.0) + method_source + rake (>= 0.8.7) + thor (>= 0.20.3, < 2.0) + rake (13.0.0) + rb-fsevent (0.10.3) + rb-inotify (0.10.0) + ffi (~> 1.0) + redis (4.1.3) + redis-actionpack (5.1.0) + actionpack (>= 4.0, < 7) + redis-rack (>= 1, < 3) + redis-store (>= 1.1.0, < 2) + redis-activesupport (5.2.0) + activesupport (>= 3, < 7) + redis-store (>= 1.3, < 2) + redis-rack (2.0.6) + rack (>= 1.5, < 3) + redis-store (>= 1.2, < 2) + redis-rails (5.0.2) + redis-actionpack (>= 5.0, < 6) + redis-activesupport (>= 5.0, < 6) + redis-store (>= 1.2, < 2) + redis-store (1.8.0) + redis (>= 4, < 5) + regexp_parser (1.6.0) + responders (3.0.0) + actionpack (>= 5.0) + railties (>= 5.0) + ruby_dep (1.5.0) + rubyzip (2.0.0) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.1.0) + railties (>= 5.2.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + selenium-webdriver (3.142.6) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) + spring (2.1.0) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (0.20.3) + thread_safe (0.3.6) + tilt (2.0.10) + turbolinks (5.2.1) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uglifier (4.2.0) + execjs (>= 0.3.0, < 3) + warden (1.2.8) + rack (>= 2.0.6) + web-console (4.0.1) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + websocket-driver (0.7.1) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.4) + xpath (3.2.0) + nokogiri (~> 1.8) + zeitwerk (2.2.0) + +PLATFORMS + ruby + +DEPENDENCIES + bootsnap (>= 1.1.0) + byebug + capybara (>= 2.15) + chromedriver-helper + coffee-rails (~> 4.2) + devise + faker! + jbuilder (~> 2.5) + listen (>= 3.0.5, < 3.2) + pg (>= 0.18, < 2.0) + puma (~> 3.11) + rails (~> 6.0.0.rc1) + redis-rails + sass-rails (~> 5.0) + selenium-webdriver + spring + spring-watcher-listen (~> 2.0.0) + turbolinks (~> 5) + tzinfo-data + uglifier (>= 1.3.0) + web-console (>= 3.3.0) + +RUBY VERSION + ruby 2.5.7p206 + +BUNDLED WITH + 1.17.3 diff --git a/psql/query.go b/psql/query.go index 8d70e43..70ea0a2 100644 --- a/psql/query.go +++ b/psql/query.go @@ -435,10 +435,24 @@ func (c *compilerContext) renderJoinedColumns(sel *qcode.Select, ti *DBTableInfo } childSel := &c.s[id] + cti, err := c.schema.GetTable(childSel.Table) + if err != nil { + continue + } + //fmt.Fprintf(w, `"%s_%d_join"."%s" AS "%s"`, //s.Table, s.ID, s.Table, s.FieldName) - colWithTableIDSuffixAlias(c.w, childSel.Table, childSel.ID, - "_join", childSel.Table, childSel.FieldName) + if cti.Singular { + c.w.WriteString(`"sel_json_`) + int2string(c.w, childSel.ID) + c.w.WriteString(`" AS "`) + c.w.WriteString(childSel.FieldName) + c.w.WriteString(`"`) + + } else { + colWithTableIDSuffixAlias(c.w, childSel.Table, childSel.ID, + "_join", childSel.Table, childSel.FieldName) + } } return nil diff --git a/qcode/qcode.go b/qcode/qcode.go index 05e8e98..30bc724 100644 --- a/qcode/qcode.go +++ b/qcode/qcode.go @@ -287,10 +287,7 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error { continue } - trv, ok := com.tr[role][field.Name] - if !ok { - continue - } + trv := com.getRole(role, field.Name) selects = append(selects, Select{ ID: id, @@ -739,6 +736,16 @@ func (com *Compiler) compileArgOffset(sel *Select, arg *Arg) error { return nil } +var zeroTrv = &trval{} + +func (com *Compiler) getRole(role, field string) *trval { + if trv, ok := com.tr[role][field]; ok { + return trv + } else { + return zeroTrv + } +} + func newExp(st *util.Stack, node *Node, usePool bool) (*Exp, error) { name := node.Name if name[0] == '_' { diff --git a/serv/cmd.go b/serv/cmd.go index 87c1660..e73b4fb 100644 --- a/serv/cmd.go +++ b/serv/cmd.go @@ -10,7 +10,6 @@ import ( "github.com/dosco/super-graph/qcode" "github.com/gobuffalo/flect" "github.com/jackc/pgx/v4" - "github.com/jackc/pgx/v4/log/zerologadapter" "github.com/jackc/pgx/v4/pgxpool" "github.com/rs/zerolog" "github.com/spf13/cobra" @@ -217,7 +216,7 @@ func initDB(c *config, useDB bool) (*pgx.Conn, error) { config.LogLevel = pgx.LogLevelNone } - config.Logger = zerologadapter.NewLogger(*logger) + config.Logger = NewSQLLogger(*logger) db, err := pgx.ConnectConfig(context.Background(), config) if err != nil { @@ -252,7 +251,7 @@ func initDBPool(c *config) (*pgxpool.Pool, error) { config.ConnConfig.LogLevel = pgx.LogLevelNone } - config.ConnConfig.Logger = zerologadapter.NewLogger(*logger) + config.ConnConfig.Logger = NewSQLLogger(*logger) // if c.DB.MaxRetries != 0 { // opt.MaxRetries = c.DB.MaxRetries diff --git a/serv/cmd_seed.go b/serv/cmd_seed.go index f0cc2d4..f9b152e 100644 --- a/serv/cmd_seed.go +++ b/serv/cmd_seed.go @@ -67,7 +67,7 @@ func graphQLFunc(query string, data interface{}) map[string]interface{} { c.req.Query = query c.req.Vars = b - res, err := c.execQuery() + res, err := c.execQuery("user") if err != nil { logger.Fatal().Err(err).Msg("graphql query failed") } diff --git a/serv/core.go b/serv/core.go index 8da4eab..edc4779 100644 --- a/serv/core.go +++ b/serv/core.go @@ -32,7 +32,15 @@ func (c *coreContext) handleReq(w io.Writer, req *http.Request) error { c.req.ref = req.Referer() c.req.hdr = req.Header - b, err := c.execQuery() + var role string + + if authCheck(c) { + role = "user" + } else { + role = "anon" + } + + b, err := c.execQuery(role) if err != nil { return err } @@ -40,12 +48,14 @@ func (c *coreContext) handleReq(w io.Writer, req *http.Request) error { return c.render(w, b) } -func (c *coreContext) execQuery() ([]byte, error) { +func (c *coreContext) execQuery(role string) ([]byte, error) { var err error var skipped uint32 var qc *qcode.QCode var data []byte + logger.Debug().Str("role", role).Msg(c.req.Query) + if conf.UseAllowList { var ps *preparedItem @@ -59,7 +69,7 @@ func (c *coreContext) execQuery() ([]byte, error) { } else { - qc, err = qcompile.Compile([]byte(c.req.Query), "user") + qc, err = qcompile.Compile([]byte(c.req.Query), role) if err != nil { return nil, err } diff --git a/serv/http.go b/serv/http.go index c943110..d419d8b 100644 --- a/serv/http.go +++ b/serv/http.go @@ -94,42 +94,7 @@ func apiv1Http(w http.ResponseWriter, r *http.Request) { } if strings.EqualFold(ctx.req.OpName, introspectionQuery) { - // dat, err := ioutil.ReadFile("test.schema") - // if err != nil { - // http.Error(w, err.Error(), http.StatusInternalServerError) - // return - // } - //w.Write(dat) - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`{ - "data": { - "__schema": { - "queryType": { - "name": "Query" - }, - "mutationType": null, - "subscriptionType": null - } - }, - "extensions":{ - "tracing":{ - "version":1, - "startTime":"2019-06-04T19:53:31.093Z", - "endTime":"2019-06-04T19:53:31.108Z", - "duration":15219720, - "execution": { - "resolvers": [{ - "path": ["__schema"], - "parentType": "Query", - "fieldName": "__schema", - "returnType": "__Schema!", - "startOffset": 50950, - "duration": 17187 - }] - } - } - } - }`)) + introspect(w) return } diff --git a/serv/introsp.go b/serv/introsp.go new file mode 100644 index 0000000..2fbf26f --- /dev/null +++ b/serv/introsp.go @@ -0,0 +1,36 @@ +package serv + +import "net/http" + +func introspect(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null + } + }, + "extensions":{ + "tracing":{ + "version":1, + "startTime":"2019-06-04T19:53:31.093Z", + "endTime":"2019-06-04T19:53:31.108Z", + "duration":15219720, + "execution": { + "resolvers": [{ + "path": ["__schema"], + "parentType": "Query", + "fieldName": "__schema", + "returnType": "__Schema!", + "startOffset": 50950, + "duration": 17187 + }] + } + } + } + }`)) +} diff --git a/serv/prepare.go b/serv/prepare.go index ac0eb5f..697d80a 100644 --- a/serv/prepare.go +++ b/serv/prepare.go @@ -30,7 +30,7 @@ func initPreparedList() { for k, v := range _allowList.list { err := prepareStmt(k, v.gql, v.vars) if err != nil { - logger.Warn().Err(err).Send() + logger.Warn().Str("gql", v.gql).Err(err).Send() } } } diff --git a/serv/sqllog.go b/serv/sqllog.go new file mode 100644 index 0000000..3fccbea --- /dev/null +++ b/serv/sqllog.go @@ -0,0 +1,45 @@ +package serv + +import ( + "context" + + "github.com/jackc/pgx/v4" + "github.com/rs/zerolog" +) + +type Logger struct { + logger zerolog.Logger +} + +// NewLogger accepts a zerolog.Logger as input and returns a new custom pgx +// logging fascade as output. +func NewSQLLogger(logger zerolog.Logger) *Logger { + return &Logger{ + logger: logger.With().Logger(), + } +} + +func (pl *Logger) Log(ctx context.Context, level pgx.LogLevel, msg string, data map[string]interface{}) { + var zlevel zerolog.Level + switch level { + case pgx.LogLevelNone: + zlevel = zerolog.NoLevel + case pgx.LogLevelError: + zlevel = zerolog.ErrorLevel + case pgx.LogLevelWarn: + zlevel = zerolog.WarnLevel + case pgx.LogLevelInfo: + zlevel = zerolog.InfoLevel + case pgx.LogLevelDebug: + zlevel = zerolog.DebugLevel + default: + zlevel = zerolog.DebugLevel + } + + if sql, ok := data["sql"]; ok { + delete(data, "sql") + pl.logger.WithLevel(zlevel).Fields(data).Msg(sql.(string)) + } else { + pl.logger.WithLevel(zlevel).Fields(data).Msg(msg) + } +}