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

354 lines
11 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
, buildInsPermInfo
, SelPerm(..)
, SelPermDef
, CreateSelPerm
, buildSelPermInfo
, UpdPerm(..)
, UpdPermDef
, CreateUpdPerm
, buildUpdPermInfo
, DelPerm(..)
, DelPermDef
, CreateDelPerm
, buildDelPermInfo
, IsPerm(..)
, addPermP2
, DropPerm
, runDropPerm
, SetPermComment(..)
, runSetPermComment
, fetchPermDef
2018-06-27 16:11:32 +03:00
) where
import Hasura.EncJSON
import Hasura.Incremental (Cacheable)
import Hasura.Prelude
2018-06-27 16:11:32 +03:00
import Hasura.RQL.DDL.Permission.Internal
import Hasura.RQL.DML.Internal hiding (askPermInfo)
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 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
{ ipCheck :: !BoolExp
, ipSet :: !(Maybe (ColumnValues Value))
, ipColumns :: !(Maybe PermColSpec)
} deriving (Show, Eq, Lift, Generic)
instance Cacheable InsPerm
2018-06-27 16:11:32 +03:00
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''InsPerm)
type InsPermDef = PermDef InsPerm
type CreateInsPerm = CreatePerm InsPerm
procSetObj
:: (QErrM m)
=> QualifiedTable
-> FieldInfoMap FieldInfo
-> Maybe (ColumnValues Value)
2019-04-17 12:48:41 +03:00
-> m (PreSetColsPartial, [Text], [SchemaDependency])
procSetObj tn fieldInfoMap mObj = do
(setColTups, deps) <- withPathK "set" $
fmap unzip $ forM (HM.toList setObj) $ \(pgCol, val) -> do
ty <- askPGType fieldInfoMap pgCol $
"column " <> pgCol <<> " not found in table " <>> tn
sqlExp <- valueParser (PGTypeScalar ty) val
let dep = mkColDep (getDepReason sqlExp) tn pgCol
return ((pgCol, sqlExp), dep)
return (HM.fromList setColTups, depHeaders, deps)
where
setObj = fromMaybe mempty mObj
depHeaders = getDepHeadersFromVal $ Object $
HM.fromList $ map (first getPGColTxt) $ HM.toList setObj
getDepReason = bool DRSessionVariable DROnType . isStaticValue
2018-06-27 16:11:32 +03:00
buildInsPermInfo
:: (QErrM m, TableCoreInfoRM m)
=> QualifiedTable
-> FieldInfoMap FieldInfo
2018-06-27 16:11:32 +03:00
-> PermDef InsPerm
-> m (WithDeps InsPermInfo)
buildInsPermInfo tn fieldInfoMap (PermDef _rn (InsPerm chk set mCols) _) =
2019-04-17 12:48:41 +03:00
withPathK "permission" $ do
(be, beDeps) <- withPathK "check" $ procBoolExp tn fieldInfoMap chk
(setColsSQL, setHdrs, setColDeps) <- procSetObj tn fieldInfoMap set
void $ withPathK "columns" $ indexedForM insCols $ \col ->
askPGType fieldInfoMap col ""
let fltrHeaders = getDependentHeaders chk
reqHdrs = fltrHeaders `union` setHdrs
insColDeps = map (mkColDep DRUntyped tn) insCols
deps = mkParentDep tn : beDeps ++ setColDeps ++ insColDeps
insColsWithoutPresets = insCols \\ HM.keys setColsSQL
return (InsPermInfo (HS.fromList insColsWithoutPresets) be setColsSQL reqHdrs, deps)
2018-06-27 16:11:32 +03:00
where
allCols = map pgiColumn $ getCols fieldInfoMap
insCols = fromMaybe allCols $ convColSpec fieldInfoMap <$> mCols
2018-06-27 16:11:32 +03:00
type instance PermInfo InsPerm = InsPermInfo
instance IsPerm InsPerm where
permAccessor = PAInsert
buildPermInfo = buildInsPermInfo
-- Select constraint
data SelPerm
= SelPerm
{ spColumns :: !PermColSpec -- ^ Allowed columns
, spFilter :: !BoolExp -- ^ Filter expression
, spLimit :: !(Maybe Int) -- ^ Limit value
, spAllowAggregations :: !Bool -- ^ Allow aggregation
, spComputedFields :: ![ComputedFieldName] -- ^ Allowed computed fields
} deriving (Show, Eq, Lift, Generic)
instance Cacheable SelPerm
$(deriveToJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''SelPerm)
2018-06-27 16:11:32 +03:00
instance FromJSON SelPerm where
parseJSON = withObject "SelPerm" $ \o ->
SelPerm
<$> o .: "columns"
<*> o .: "filter"
<*> o .:? "limit"
<*> o .:? "allow_aggregations" .!= False
<*> o .:? "computed_fields" .!= []
2018-06-27 16:11:32 +03:00
buildSelPermInfo
:: (QErrM m, TableCoreInfoRM m)
=> QualifiedTable
-> FieldInfoMap FieldInfo
2018-06-27 16:11:32 +03:00
-> SelPerm
-> m (WithDeps SelPermInfo)
buildSelPermInfo tn fieldInfoMap sp = withPathK "permission" $ do
2018-06-27 16:11:32 +03:00
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
-- validate computed fields
scalarComputedFields <-
withPathK "computed_fields" $ indexedForM computedFields $ \fieldName -> do
computedFieldInfo <- askComputedFieldInfo fieldInfoMap fieldName
case _cfiReturnType computedFieldInfo of
CFRScalar _ -> pure fieldName
CFRSetofTable returnTable -> throw400 NotSupported $
"select permissions on computed field " <> fieldName
<<> " are auto-derived from the permissions on its returning table "
<> returnTable <<> " and cannot be specified manually"
let deps = mkParentDep tn : beDeps ++ map (mkColDep DRUntyped tn) pgCols
++ map (mkComputedFieldDep DRUntyped tn) scalarComputedFields
2018-06-27 16:11:32 +03:00
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) (HS.fromList computedFields)
tn be mLimit allowAgg depHeaders
, deps
)
2018-06-27 16:11:32 +03:00
where
allowAgg = spAllowAggregations sp
computedFields = spComputedFields 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 instance PermInfo SelPerm = SelPermInfo
instance IsPerm SelPerm where
permAccessor = PASelect
buildPermInfo tn fieldInfoMap (PermDef _ a _) =
buildSelPermInfo tn fieldInfoMap a
2018-06-27 16:11:32 +03:00
-- Update constraint
data UpdPerm
= UpdPerm
{ ucColumns :: !PermColSpec -- Allowed columns
, ucSet :: !(Maybe (ColumnValues Value)) -- Preset columns
2018-06-27 16:11:32 +03:00
, ucFilter :: !BoolExp -- Filter expression
} deriving (Show, Eq, Lift, Generic)
instance Cacheable UpdPerm
2018-06-27 16:11:32 +03:00
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''UpdPerm)
type UpdPermDef = PermDef UpdPerm
type CreateUpdPerm = CreatePerm UpdPerm
2018-06-27 16:11:32 +03:00
buildUpdPermInfo
:: (QErrM m, TableCoreInfoRM m)
=> QualifiedTable
-> FieldInfoMap FieldInfo
2018-06-27 16:11:32 +03:00
-> UpdPerm
-> m (WithDeps UpdPermInfo)
buildUpdPermInfo tn fieldInfoMap (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 tn fieldInfoMap set
2018-06-27 16:11:32 +03:00
-- check if the columns exist
void $ withPathK "columns" $ indexedForM updCols $ \updCol ->
2018-06-27 16:11:32 +03:00
askPGType fieldInfoMap updCol relInUpdErr
let updColDeps = map (mkColDep DRUntyped 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
updCols = convColSpec fieldInfoMap colSpec
relInUpdErr = "relationships can't be used in update"
type instance PermInfo UpdPerm = UpdPermInfo
instance IsPerm UpdPerm where
permAccessor = PAUpdate
buildPermInfo tn fieldInfoMap (PermDef _ a _) =
buildUpdPermInfo tn fieldInfoMap a
2018-06-27 16:11:32 +03:00
-- Delete permission
data DelPerm
= DelPerm { dcFilter :: !BoolExp }
deriving (Show, Eq, Lift, Generic)
instance Cacheable DelPerm
2018-06-27 16:11:32 +03:00
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''DelPerm)
type DelPermDef = PermDef DelPerm
type CreateDelPerm = CreatePerm DelPerm
buildDelPermInfo
:: (QErrM m, TableCoreInfoRM m)
=> QualifiedTable
-> FieldInfoMap FieldInfo
2018-06-27 16:11:32 +03:00
-> DelPerm
-> m (WithDeps DelPermInfo)
buildDelPermInfo tn fieldInfoMap (DelPerm 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
let deps = mkParentDep tn : beDeps
depHeaders = getDependentHeaders fltr
return (DelPermInfo tn be depHeaders, deps)
2018-06-27 16:11:32 +03:00
type instance PermInfo DelPerm = DelPermInfo
instance IsPerm DelPerm where
permAccessor = PADelete
buildPermInfo tn fieldInfoMap (PermDef _ a _) =
buildDelPermInfo tn fieldInfoMap a
2018-06-27 16:11:32 +03:00
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
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 :: MonadTx m => QualifiedTable -> RoleName -> PermType -> m ()
purgePerm qt rn pt =
case pt of
PTInsert -> dropPermP2 @InsPerm dp
PTSelect -> dropPermP2 @SelPerm dp
PTUpdate -> dropPermP2 @UpdPerm dp
PTDelete -> dropPermP2 @DelPerm dp
2018-06-27 16:11:32 +03:00
where
dp :: DropPerm a
dp = DropPerm qt rn
fetchPermDef
:: QualifiedTable
-> RoleName
-> PermType
-> Q.TxE QErr (Value, Maybe T.Text)
fetchPermDef (QualifiedObject sn tn) rn pt =
(first Q.getAltJ . Q.getRow) <$> Q.withQE defaultTxErrorHandler
[Q.sql|
SELECT perm_def::json, comment
FROM hdb_catalog.hdb_permission
WHERE table_schema = $1
AND table_name = $2
AND role_name = $3
AND perm_type = $4
|] (sn, tn, rn, permTypeToCode pt) True