这是indexloc提供的服务,不要输入任何密码
Skip to content

fix insert fails for non-admin roles on v1/query (fix #327) #328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions server/src-lib/Hasura/GraphQL/Resolve/Context.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

module Hasura.GraphQL.Resolve.Context
( FieldMap
Expand All @@ -26,9 +25,6 @@ module Hasura.GraphQL.Resolve.Context
, module Hasura.GraphQL.Utils
) where

import Data.Aeson
import Data.Aeson.Casing
import Data.Aeson.TH
import Data.Has
import Hasura.Prelude

Expand Down Expand Up @@ -68,13 +64,6 @@ type OrdByResolveCtxElem = (PGColInfo, OrdTy, NullsOrder)
type OrdByResolveCtx
= Map.HashMap (G.NamedType, G.EnumValue) OrdByResolveCtxElem

data InsertTxConflictCtx
= InsertTxConflictCtx
{ itcAction :: !ConflictAction
, itcConstraint :: !(Maybe ConstraintName)
} deriving (Show, Eq)
$(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''InsertTxConflictCtx)

getFldInfo
:: (MonadError QErr m, MonadReader r m, Has FieldMap r)
=> G.NamedType -> G.Name -> m (Either PGColInfo (RelInfo, S.BoolExp, Maybe Int, Bool))
Expand Down
38 changes: 6 additions & 32 deletions server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ module Hasura.GraphQL.Resolve.Mutation
import Data.Has
import Hasura.Prelude

import qualified Data.Aeson.Text as AT
import qualified Data.ByteString.Builder as BB
import qualified Data.HashMap.Strict as Map
import qualified Data.Text.Lazy as LT
import qualified Database.PG.Query as Q
import qualified Language.GraphQL.Draft.Syntax as G

import qualified Hasura.RQL.DML.Delete as RD
Expand Down Expand Up @@ -73,12 +69,10 @@ convertRowObj val =
let prepExp = fromMaybe (S.SEUnsafe "NULL") prepExpM
return (PGCol $ G.unName k, prepExp)

type ConflictCtx = (ConflictAction, Maybe ConstraintName)

mkConflictClause
:: (MonadError QErr m)
=> [PGCol]
-> ConflictCtx
-> RI.ConflictCtx
-> m RI.ConflictClauseP1
mkConflictClause cols (act, conM) = case (act , conM) of
(CAIgnore, Nothing) -> return $ RI.CP1DoNothing Nothing
Expand Down Expand Up @@ -112,7 +106,7 @@ parseConstraint obj = do

parseOnConflict
:: (MonadError QErr m)
=> AnnGValue -> m ConflictCtx
=> AnnGValue -> m RI.ConflictCtx
parseOnConflict val =
flip withObject val $ \_ obj -> do
action <- parseAction obj
Expand All @@ -129,12 +123,13 @@ convertInsert role (tn, vn) tableCols fld = do
rows <- withArg arguments "objects" asRowExps
conflictCtxM <- withPathK "on_conflict" $
withArgM arguments "on_conflict" parseOnConflict
onConflictM <- mapM (mkConflictClause tableCols) conflictCtxM
mutFlds <- convertMutResp (_fType fld) $ _fSelSet fld
args <- get
let p1Query = RI.InsertQueryP1 tn vn tableCols rows onConflictM mutFlds
p1 = (p1Query, args)
return $
bool (nonAdminInsert args rows conflictCtxM mutFlds)
(adminInsert args rows conflictCtxM mutFlds)
$ isAdmin role
bool (RI.nonAdminInsert p1) (RI.insertP2 p1) $ isAdmin role
where
arguments = _fArguments fld
asRowExps = withArray (const $ mapM rowExpWithDefaults)
Expand All @@ -144,27 +139,6 @@ convertInsert role (tn, vn) tableCols fld = do

defVals = Map.fromList $ zip tableCols (repeat $ S.SEUnsafe "DEFAULT")

adminInsert args rows conflictCtxM mutFlds = do
onConflictM <- mapM (mkConflictClause tableCols) conflictCtxM
let p1 = RI.InsertQueryP1 tn vn tableCols rows onConflictM mutFlds
RI.insertP2 (p1, args)

nonAdminInsert args rows conflictCtxM mutFlds = do
mapM_ (mkConflictClause tableCols) conflictCtxM
setConflictCtxTx conflictCtxM
let p1 = RI.InsertQueryP1 tn vn tableCols rows Nothing mutFlds
RI.insertP2 (p1, args)

setConflictCtxTx conflictCtxM = do
let t = maybe "null" conflictCtxToJSON conflictCtxM
setVal = toSQL $ S.SELit t
setVar = BB.string7 "SET LOCAL hasura.conflict_clause = "
q = Q.fromBuilder $ setVar <> setVal
Q.unitQE defaultTxErrorHandler q () False

conflictCtxToJSON (act, constrM) =
LT.toStrict $ AT.encodeToLazyText $ InsertTxConflictCtx act constrM

type ApplySQLOp = (PGCol, S.SQLExp) -> S.SQLExp

rhsExpOp :: S.SQLOp -> S.AnnType -> ApplySQLOp
Expand Down
45 changes: 44 additions & 1 deletion server/src-lib/Hasura/RQL/DML/Insert.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ module Hasura.RQL.DML.Insert where
import Data.Aeson.Types
import Instances.TH.Lift ()

import qualified Data.Aeson.Text as AT
import qualified Data.ByteString.Builder as BB
import qualified Data.HashMap.Strict as HM
import qualified Data.Sequence as DS
import qualified Data.Text.Lazy as LT

import Hasura.Prelude
import Hasura.RQL.DML.Internal
Expand Down Expand Up @@ -172,6 +175,7 @@ convInsertQuery objsParser prepFn (InsertQuery tableName val oC mRetCols) = do
unless (ipiAllowUpsert insPerm) $ throw400 PermissionDenied $
"upsert is not allowed for role" <>> roleName
buildConflictClause tableInfo c

return $ InsertQueryP1 tableName insView insCols insTuples
conflictClause mutFlds

Expand All @@ -198,11 +202,50 @@ insertP2 (u, p) =
where
insertSQL = toSQL $ mkSQLInsert u

type ConflictCtx = (ConflictAction, Maybe ConstraintName)

nonAdminInsert :: (InsertQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
nonAdminInsert (insQueryP1, args) = do
conflictCtxM <- mapM extractConflictCtx conflictClauseP1
setConflictCtx conflictCtxM
insertP2 (withoutConflictClause, args)
where
withoutConflictClause = insQueryP1{iqp1Conflict=Nothing}
conflictClauseP1 = iqp1Conflict insQueryP1

extractConflictCtx :: (MonadError QErr m) => ConflictClauseP1 -> m ConflictCtx
extractConflictCtx cp =
case cp of
(CP1DoNothing mConflictTar) -> do
mConstraintName <- mapM extractConstraintName mConflictTar
return (CAIgnore, mConstraintName)
(CP1Update conflictTar _) -> do
constraintName <- extractConstraintName conflictTar
return (CAUpdate, Just constraintName)
where
extractConstraintName (Constraint cn) = return cn
extractConstraintName _ = throw400 NotSupported
"\"constraint_on\" not supported for non admin insert. use \"constraint\" instead"

setConflictCtx :: Maybe ConflictCtx -> Q.TxE QErr ()
setConflictCtx conflictCtxM = do
let t = maybe "null" conflictCtxToJSON conflictCtxM
setVal = toSQL $ S.SELit t
setVar = BB.string7 "SET LOCAL hasura.conflict_clause = "
q = Q.fromBuilder $ setVar <> setVal
Q.unitQE defaultTxErrorHandler q () False
where
conflictCtxToJSON (act, constrM) =
LT.toStrict $ AT.encodeToLazyText $ InsertTxConflictCtx act constrM

instance HDBQuery InsertQuery where

type Phase1Res InsertQuery = (InsertQueryP1, DS.Seq Q.PrepArg)
phaseOne = convInsQ

phaseTwo _ = liftTx . insertP2
phaseTwo _ p1Res = do
role <- userRole <$> ask
liftTx $
bool (nonAdminInsert p1Res) (insertP2 p1Res) $ isAdmin role

schemaCachePolicy = SCPNoChange
9 changes: 9 additions & 0 deletions server/src-lib/Hasura/RQL/Types/DML.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ module Hasura.RQL.Types.DML
, ConflictAction(..)
, ConstraintOn(..)

, InsertTxConflictCtx(..)

, UpdVals
, UpdateQuery(..)

Expand Down Expand Up @@ -268,6 +270,13 @@ data InsertQuery

$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''InsertQuery)

data InsertTxConflictCtx
= InsertTxConflictCtx
{ itcAction :: !ConflictAction
, itcConstraint :: !(Maybe ConstraintName)
} deriving (Show, Eq)
$(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''InsertTxConflictCtx)

type UpdVals = M.HashMap PGCol Value

data UpdateQuery
Expand Down
4 changes: 4 additions & 0 deletions server/test/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ querySpecFiles =
, "create_address_resident_relationship_error.yaml"
, "create_user_permission_address.yaml"
, "create_author_permission_role_admin_error.yaml"
, "create_user_permission_test_table.yaml"
, "all_json_queries.yaml"
, "upsert_role_user.yaml"
, "upsert_role_user_error.yaml"
]

gqlSpecFiles :: [FilePath]
Expand Down
56 changes: 56 additions & 0 deletions server/test/testcases/all_json_queries.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
description: Select, Insert, Upsert, Update and Delete JSON queries
url: /v1/query
status: 200
query:
type: bulk
args:
- type: select
args:
table: author
columns:
- id
- name
- type: insert
args:
table: test_table
objects:
- name: erlich
age: 30
- name: gilfoyle
age: 27
returing:
- id
- name
- age
- type: update
args:
table: test_table
where:
id: 1
$set:
age: 31
returing:
- id
- name
- age
- type: insert
args:
table: test_table
objects:
- id: 2
name: galvin
age: 45
on_conflict:
constraint_on:
- id
action: update
returing:
- id
- name
- age
- type: delete
args:
table: test_table
where:
id: 2

8 changes: 8 additions & 0 deletions server/test/testcases/create_tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,11 @@ query:
placed TIMESTAMPTZ NOT NULL,
shipped TIMESTAMPTZ
)
- type: run_sql
args:
sql: |
CREATE TABLE test_table (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER NOT NULL
)
12 changes: 12 additions & 0 deletions server/test/testcases/create_user_permission_test_table.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
description: Create a insert permission on test_table for user role
url: /v1/query
status: 200
query:
type: create_insert_permission
args:
table: test_table
role: user
permission:
check:
id: X-Hasura-User-Id
allow_upsert: true
4 changes: 4 additions & 0 deletions server/test/testcases/track_tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ query:
args:
schema: public
name: orders
- type: track_table
args:
schema: public
name: test_table

17 changes: 17 additions & 0 deletions server/test/testcases/upsert_role_user.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
description: upsert into test_table with user role
url: v1/query
headers:
X-Hasura-Role: user
X-Hasura-User-Id: '1'
status: 200
query:
type: insert
args:
table: test_table
objects:
- id: 1
name: monika
age: 25
on_conflict:
constraint: test_table_pkey
action: update
19 changes: 19 additions & 0 deletions server/test/testcases/upsert_role_user_error.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
description: Upsert on test_table as user role with constraint on columns (error)
url: /v1/query
headers:
X-Hasura-Role: user
X-Hasura-User-Id: '1'
status: 400
query:
type: insert
args:
table: test_table
objects:
- id: 1
name: monika
age: 25
on_conflict:
constraint_on:
- id
action: ignore