graphql-engine/server/src-lib/Hasura/RQL/DDL/Permission.hs

441 lines
12 KiB
Haskell
Raw Normal View History

2018-06-27 16:11:32 +03:00
module Hasura.RQL.DDL.Permission
( CreatePerm
, runCreatePerm
2018-06-27 16:11:32 +03:00
, purgePerm
, PermDef(..)
, InsPerm(..)
, InsPermDef
, CreateInsPerm
, clearInsInfra
, buildInsInfra
, buildInsPermInfo
, DropInsPerm
, dropInsPermP2
, SelPerm(..)
, SelPermDef
, CreateSelPerm
, buildSelPermInfo
, DropSelPerm
, dropSelPermP2
, UpdPerm(..)
, UpdPermDef
, CreateUpdPerm
, buildUpdPermInfo
, DropUpdPerm
, dropUpdPermP2
, DelPerm(..)
, DelPermDef
, CreateDelPerm
, buildDelPermInfo
, DropDelPerm
, dropDelPermP2
, IsPerm(..)
, addPermP1
, addPermP2
, dropView
, DropPerm
, runDropPerm
, SetPermComment(..)
, runSetPermComment
2018-06-27 16:11:32 +03:00
) where
import Hasura.EncJSON
import Hasura.Prelude
2018-06-27 16:11:32 +03:00
import Hasura.RQL.DDL.Permission.Internal
import Hasura.RQL.DDL.Permission.Triggers
import Hasura.RQL.DML.Internal
import Hasura.RQL.GBoolExp
import Hasura.RQL.Types
2018-06-27 16:11:32 +03:00
import Hasura.SQL.Types
import qualified Database.PG.Query as Q
2018-06-27 16:11:32 +03:00
import qualified Hasura.SQL.DML as S
import Control.Arrow (first)
import Data.Aeson
2018-06-27 16:11:32 +03:00
import Data.Aeson.Casing
import Data.Aeson.TH
import Language.Haskell.TH.Syntax (Lift)
2018-06-27 16:11:32 +03:00
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
import qualified Data.Text as T
2018-06-27 16:11:32 +03:00
-- Insert permission
data InsPerm
= InsPerm
{ icCheck :: !BoolExp
, icSet :: !(Maybe ColVals)
, icColumns :: !(Maybe PermColSpec)
2018-06-27 16:11:32 +03:00
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''InsPerm)
type InsPermDef = PermDef InsPerm
type CreateInsPerm = CreatePerm InsPerm
buildViewName :: QualifiedTable -> RoleName -> PermType -> QualifiedTable
buildViewName (QualifiedObject sn tn) (RoleName rTxt) pt =
QualifiedObject hdbViewsSchema $ TableName
2018-06-27 16:11:32 +03:00
(rTxt <> "__" <> T.pack (show pt) <> "__" <> snTxt <> "__" <> tnTxt)
where
hdbViewsSchema = SchemaName "hdb_views"
snTxt = getSchemaTxt sn
tnTxt = getTableTxt tn
buildView :: QualifiedTable -> QualifiedTable -> Q.Query
buildView tn vn =
Q.fromBuilder $ mconcat
[ "CREATE VIEW " <> toSQL vn
, " AS SELECT * FROM " <> toSQL tn
2018-06-27 16:11:32 +03:00
]
dropView :: QualifiedTable -> Q.Tx ()
dropView vn =
Q.unitQ dropViewS () False
where
dropViewS = Q.fromBuilder $
"DROP VIEW " <> toSQL vn
2018-06-27 16:11:32 +03:00
procSetObj
:: (QErrM m)
=> TableInfo -> Maybe ColVals -> m (PreSetCols, [Text], [SchemaDependency])
procSetObj ti mObj = do
setColsSQL <- withPathK "set" $
fmap HM.fromList $ forM (HM.toList setObj) $ \(pgCol, val) -> do
ty <- askPGType fieldInfoMap pgCol $
"column " <> pgCol <<> " not found in table " <>> tn
sqlExp <- valueParser ty val
return (pgCol, sqlExp)
let deps = map (mkColDep "on_type" tn . fst) $ HM.toList setColsSQL
return (setColsSQL, depHeaders, deps)
where
fieldInfoMap = tiFieldInfoMap ti
tn = tiName ti
setObj = fromMaybe mempty mObj
depHeaders = getDepHeadersFromVal $ Object $
HM.fromList $ map (first getPGColTxt) $ HM.toList setObj
2018-06-27 16:11:32 +03:00
buildInsPermInfo
:: (QErrM m, CacheRM m)
=> TableInfo
-> PermDef InsPerm
-> m (WithDeps InsPermInfo)
buildInsPermInfo tabInfo (PermDef rn (InsPerm chk set mCols) _) = withPathK "permission" $ do
2018-06-27 16:11:32 +03:00
(be, beDeps) <- withPathK "check" $
-- procBoolExp tn fieldInfoMap (S.QualVar "NEW") chk
procBoolExp tn fieldInfoMap chk
let fltrHeaders = getDependentHeaders chk
(setColsSQL, setHdrs, setColDeps) <- procSetObj tabInfo set
let reqHdrs = fltrHeaders `union` setHdrs
deps = mkParentDep tn : beDeps ++ setColDeps
preSetCols <- HM.union setColsSQL <$> nonInsColVals
return (InsPermInfo vn be preSetCols reqHdrs, deps)
2018-06-27 16:11:32 +03:00
where
fieldInfoMap = tiFieldInfoMap tabInfo
tn = tiName tabInfo
vn = buildViewName tn rn PTInsert
allCols = map pgiName $ getCols fieldInfoMap
nonInsCols = case mCols of
Nothing -> return []
Just cols -> do
let insCols = convColSpec fieldInfoMap cols
withPathK "columns" $
indexedForM_ insCols $ \c -> void $ askPGType fieldInfoMap c ""
return $ allCols \\ insCols
nonInsColVals = S.mkColDefValMap <$> nonInsCols
2018-06-27 16:11:32 +03:00
buildInsInfra :: QualifiedTable -> InsPermInfo -> Q.TxE QErr ()
buildInsInfra tn (InsPermInfo vn be _ _) = do
trigFnQ <- buildInsTrigFn vn tn $ toSQLBoolExp (S.QualVar "NEW") be
2018-06-27 16:11:32 +03:00
Q.catchE defaultTxErrorHandler $ do
-- Create the view
Q.unitQ (buildView tn vn) () False
-- Inject defaults on the view
Q.discardQ (injectDefaults vn tn) () False
-- Construct a trigger function
Q.unitQ trigFnQ () False
2018-06-27 16:11:32 +03:00
-- Add trigger for check expression
Q.unitQ (buildInsTrig vn) () False
clearInsInfra :: QualifiedTable -> Q.TxE QErr ()
clearInsInfra vn =
Q.catchE defaultTxErrorHandler $ do
dropView vn
Q.unitQ (dropInsTrigFn vn) () False
type DropInsPerm = DropPerm InsPerm
dropInsPermP2
:: (CacheRWM m, MonadTx m)
=> DropInsPerm -> QualifiedTable -> m ()
2018-06-27 16:11:32 +03:00
dropInsPermP2 = dropPermP2
type instance PermInfo InsPerm = InsPermInfo
instance IsPerm InsPerm where
type DropPermP1Res InsPerm = QualifiedTable
permAccessor = PAInsert
buildPermInfo = buildInsPermInfo
addPermP2Setup qt _ = liftTx . buildInsInfra qt
2018-06-27 16:11:32 +03:00
buildDropPermP1Res dp =
ipiView <$> dropPermP1 dp
dropPermP2Setup _ vn =
liftTx $ clearInsInfra vn
-- Select constraint
data SelPerm
= SelPerm
{ spColumns :: !PermColSpec -- Allowed columns
, spFilter :: !BoolExp -- Filter expression
, spLimit :: !(Maybe Int) -- Limit value
, spAllowAggregations :: !(Maybe Bool) -- Allow aggregation
2018-06-27 16:11:32 +03:00
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''SelPerm)
buildSelPermInfo
:: (QErrM m, CacheRM m)
=> TableInfo
-> SelPerm
-> m (WithDeps SelPermInfo)
2018-06-27 16:11:32 +03:00
buildSelPermInfo tabInfo sp = do
let pgCols = convColSpec fieldInfoMap $ spColumns sp
(be, beDeps) <- withPathK "filter" $
procBoolExp tn fieldInfoMap $ spFilter sp
2018-06-27 16:11:32 +03:00
-- check if the columns exist
void $ withPathK "columns" $ indexedForM pgCols $ \pgCol ->
askPGType fieldInfoMap pgCol autoInferredErr
let deps = mkParentDep tn : beDeps ++ map (mkColDep "untyped" tn) pgCols
depHeaders = getDependentHeaders $ spFilter sp
mLimit = spLimit sp
withPathK "limit" $ mapM_ onlyPositiveInt mLimit
2018-06-27 16:11:32 +03:00
return (SelPermInfo (HS.fromList pgCols) tn be mLimit allowAgg depHeaders, deps)
2018-06-27 16:11:32 +03:00
where
tn = tiName tabInfo
fieldInfoMap = tiFieldInfoMap tabInfo
allowAgg = or $ spAllowAggregations sp
2018-06-27 16:11:32 +03:00
autoInferredErr = "permissions for relationships are automatically inferred"
type SelPermDef = PermDef SelPerm
type CreateSelPerm = CreatePerm SelPerm
type DropSelPerm = DropPerm SelPerm
type instance PermInfo SelPerm = SelPermInfo
dropSelPermP2
:: (CacheRWM m, MonadTx m)
=> DropSelPerm -> m ()
dropSelPermP2 dp =
dropPermP2 dp ()
2018-06-27 16:11:32 +03:00
instance IsPerm SelPerm where
type DropPermP1Res SelPerm = ()
permAccessor = PASelect
buildPermInfo ti (PermDef _ a _) =
buildSelPermInfo ti a
buildDropPermP1Res =
void . dropPermP1
addPermP2Setup _ _ _ = return ()
dropPermP2Setup _ _ = return ()
-- Update constraint
data UpdPerm
= UpdPerm
{ ucColumns :: !PermColSpec -- Allowed columns
, ucSet :: !(Maybe ColVals) -- Preset columns
2018-06-27 16:11:32 +03:00
, ucFilter :: !BoolExp -- Filter expression
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''UpdPerm)
type UpdPermDef = PermDef UpdPerm
type CreateUpdPerm = CreatePerm UpdPerm
buildUpdPermInfo
:: (QErrM m, CacheRM m)
=> TableInfo
-> UpdPerm
-> m (WithDeps UpdPermInfo)
buildUpdPermInfo tabInfo (UpdPerm colSpec set fltr) = do
2018-06-27 16:11:32 +03:00
(be, beDeps) <- withPathK "filter" $
procBoolExp tn fieldInfoMap fltr
2018-06-27 16:11:32 +03:00
(setColsSQL, setHeaders, setColDeps) <- procSetObj tabInfo set
2018-06-27 16:11:32 +03:00
-- check if the columns exist
_ <- withPathK "columns" $ indexedForM updCols $ \updCol ->
askPGType fieldInfoMap updCol relInUpdErr
let updColDeps = map (mkColDep "untyped" tn) updCols
deps = mkParentDep tn : beDeps ++ updColDeps ++ setColDeps
2018-06-27 16:11:32 +03:00
depHeaders = getDependentHeaders fltr
reqHeaders = depHeaders `union` setHeaders
updColsWithoutPreSets = updCols \\ HM.keys setColsSQL
2018-06-27 16:11:32 +03:00
return (UpdPermInfo (HS.fromList updColsWithoutPreSets) tn be setColsSQL reqHeaders, deps)
2018-06-27 16:11:32 +03:00
where
tn = tiName tabInfo
fieldInfoMap = tiFieldInfoMap tabInfo
updCols = convColSpec fieldInfoMap colSpec
relInUpdErr = "relationships can't be used in update"
type instance PermInfo UpdPerm = UpdPermInfo
type DropUpdPerm = DropPerm UpdPerm
dropUpdPermP2
:: (CacheRWM m, MonadTx m)
=> DropUpdPerm -> m ()
2018-06-27 16:11:32 +03:00
dropUpdPermP2 dp = dropPermP2 dp ()
instance IsPerm UpdPerm where
type DropPermP1Res UpdPerm = ()
permAccessor = PAUpdate
buildPermInfo ti (PermDef _ a _) =
buildUpdPermInfo ti a
addPermP2Setup _ _ _ = return ()
buildDropPermP1Res =
void . dropPermP1
dropPermP2Setup _ _ = return ()
-- Delete permission
data DelPerm
= DelPerm { dcFilter :: !BoolExp }
deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''DelPerm)
type DelPermDef = PermDef DelPerm
type CreateDelPerm = CreatePerm DelPerm
buildDelPermInfo
:: (QErrM m, CacheRM m)
=> TableInfo
-> DelPerm
-> m (WithDeps DelPermInfo)
2018-06-27 16:11:32 +03:00
buildDelPermInfo tabInfo (DelPerm fltr) = do
(be, beDeps) <- withPathK "filter" $
procBoolExp tn fieldInfoMap fltr
2018-06-27 16:11:32 +03:00
let deps = mkParentDep tn : beDeps
depHeaders = getDependentHeaders fltr
return (DelPermInfo tn be depHeaders, deps)
2018-06-27 16:11:32 +03:00
where
tn = tiName tabInfo
fieldInfoMap = tiFieldInfoMap tabInfo
type DropDelPerm = DropPerm DelPerm
dropDelPermP2 :: (CacheRWM m, MonadTx m) => DropDelPerm -> m ()
2018-06-27 16:11:32 +03:00
dropDelPermP2 dp = dropPermP2 dp ()
type instance PermInfo DelPerm = DelPermInfo
instance IsPerm DelPerm where
type DropPermP1Res DelPerm = ()
permAccessor = PADelete
buildPermInfo ti (PermDef _ a _) =
buildDelPermInfo ti a
addPermP2Setup _ _ _ = return ()
buildDropPermP1Res =
void . dropPermP1
dropPermP2Setup _ _ = return ()
data SetPermComment
= SetPermComment
{ apTable :: !QualifiedTable
, apRole :: !RoleName
, apPermission :: !PermType
, apComment :: !(Maybe T.Text)
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 2 snakeCase) ''SetPermComment)
setPermCommentP1 :: (UserInfoM m, QErrM m, CacheRM m) => SetPermComment -> m ()
2018-06-27 16:11:32 +03:00
setPermCommentP1 (SetPermComment qt rn pt _) = do
adminOnly
tabInfo <- askTabInfo qt
action tabInfo
where
action tabInfo = case pt of
PTInsert -> assertPermDefined rn PAInsert tabInfo
PTSelect -> assertPermDefined rn PASelect tabInfo
PTUpdate -> assertPermDefined rn PAUpdate tabInfo
PTDelete -> assertPermDefined rn PADelete tabInfo
setPermCommentP2 :: (QErrM m, MonadTx m) => SetPermComment -> m EncJSON
2018-06-27 16:11:32 +03:00
setPermCommentP2 apc = do
liftTx $ setPermCommentTx apc
return successMsg
runSetPermComment
:: (QErrM m, CacheRM m, MonadTx m, UserInfoM m)
=> SetPermComment -> m EncJSON
runSetPermComment defn = do
setPermCommentP1 defn
setPermCommentP2 defn
2018-06-27 16:11:32 +03:00
setPermCommentTx
:: SetPermComment
-> Q.TxE QErr ()
setPermCommentTx (SetPermComment (QualifiedObject sn tn) rn pt comment) =
2018-06-27 16:11:32 +03:00
Q.unitQE defaultTxErrorHandler [Q.sql|
UPDATE hdb_catalog.hdb_permission
SET comment = $1
WHERE table_schema = $2
AND table_name = $3
AND role_name = $4
AND perm_type = $5
|] (comment, sn, tn, rn, permTypeToCode pt) True
purgePerm
:: (CacheRWM m, MonadTx m)
=> QualifiedTable -> RoleName -> PermType -> m ()
2018-06-27 16:11:32 +03:00
purgePerm qt rn pt =
case pt of
PTInsert -> dropInsPermP2 dp $ buildViewName qt rn PTInsert
PTSelect -> dropSelPermP2 dp
PTUpdate -> dropUpdPermP2 dp
PTDelete -> dropDelPermP2 dp
where
dp :: DropPerm a
dp = DropPerm qt rn