diff --git a/console/src/components/Services/Data/DataState.js b/console/src/components/Services/Data/DataState.js
index 87bfd05891a95..5ab48727dcd4d 100644
--- a/console/src/components/Services/Data/DataState.js
+++ b/console/src/components/Services/Data/DataState.js
@@ -41,6 +41,7 @@ const defaultQueryPermissions = {
check: {},
allow_upsert: true,
set: {},
+ columns: [],
localSet: [
{
...defaultInsertSetState,
diff --git a/console/src/components/Services/Data/TableCommon/TableReducer.js b/console/src/components/Services/Data/TableCommon/TableReducer.js
index 7c28151e1ba27..961f2d3ab7750 100644
--- a/console/src/components/Services/Data/TableCommon/TableReducer.js
+++ b/console/src/components/Services/Data/TableCommon/TableReducer.js
@@ -279,7 +279,8 @@ const modifyReducer = (tableName, schemas, modifyStateOrig, action) => {
...getBasePermissionsState(
action.tableSchema,
action.role,
- action.query
+ action.query,
+ action.insertPermColumnRestriction
),
},
};
diff --git a/console/src/components/Services/Data/TablePermissions/Actions.js b/console/src/components/Services/Data/TablePermissions/Actions.js
index 5be8fd9bd719e..347aeac207663 100644
--- a/console/src/components/Services/Data/TablePermissions/Actions.js
+++ b/console/src/components/Services/Data/TablePermissions/Actions.js
@@ -49,17 +49,29 @@ export const SET_TYPE_CONFIG = 'ModifyTable/SET_TYPE_CONFIG';
/* */
-const queriesWithPermColumns = ['select', 'update'];
+const getQueriesWithPermColumns = insert => {
+ const queries = ['select', 'update'];
+ if (insert) {
+ queries.push('insert');
+ }
+ return queries;
+};
const permChangeTypes = {
save: 'update',
delete: 'delete',
};
-const permOpenEdit = (tableSchema, role, query) => ({
+const permOpenEdit = (
+ tableSchema,
+ role,
+ query,
+ insertPermColumnRestriction
+) => ({
type: PERM_OPEN_EDIT,
tableSchema,
role,
query,
+ insertPermColumnRestriction,
});
const permSetFilter = filter => ({ type: PERM_SET_FILTER, filter });
const permSetFilterSameAs = filter => ({
@@ -122,7 +134,12 @@ const setConfigValueType = value => {
: 'static';
};
-const getBasePermissionsState = (tableSchema, role, query) => {
+const getBasePermissionsState = (
+ tableSchema,
+ role,
+ query,
+ insertPermColumnRestriction
+) => {
const _permissions = JSON.parse(JSON.stringify(defaultPermissionsState));
_permissions.table = tableSchema.table_name;
@@ -139,6 +156,13 @@ const getBasePermissionsState = (tableSchema, role, query) => {
// If the query is insert, transform set object if exists to an array
if (q === 'insert') {
// If set is an object
+ if (insertPermColumnRestriction) {
+ if (!_permissions[q].columns) {
+ _permissions[q].columns = tableSchema.columns.map(
+ c => c.column_name
+ );
+ }
+ }
if ('set' in _permissions[q]) {
if (
Object.keys(_permissions[q].set).length > 0 &&
@@ -619,7 +643,7 @@ export {
permSetBulkSelect,
toggleColumn,
toggleAllColumns,
- queriesWithPermColumns,
+ getQueriesWithPermColumns,
getFilterKey,
getBasePermissionsState,
updatePermissionsState,
diff --git a/console/src/components/Services/Data/TablePermissions/Permissions.js b/console/src/components/Services/Data/TablePermissions/Permissions.js
index a23117f3b3698..bfd04d55ec7a2 100644
--- a/console/src/components/Services/Data/TablePermissions/Permissions.js
+++ b/console/src/components/Services/Data/TablePermissions/Permissions.js
@@ -9,7 +9,7 @@ import 'brace/theme/github';
import { RESET } from '../TableModify/ModifyActions';
import {
- queriesWithPermColumns,
+ getQueriesWithPermColumns,
permChangeTypes,
permOpenEdit,
permSetFilter,
@@ -417,7 +417,14 @@ class Permissions extends Component {
if (isNewPerm && permsState.newRole !== '') {
dispatch(permOpenEdit(tableSchema, permsState.newRole, queryType));
} else if (role !== '') {
- dispatch(permOpenEdit(tableSchema, role, queryType));
+ dispatch(
+ permOpenEdit(
+ tableSchema,
+ role,
+ queryType,
+ semverCheck('insertPermRestrictColumns', this.props.serverVersion)
+ )
+ );
} else {
window.alert('Please enter a role name');
}
@@ -452,21 +459,21 @@ class Permissions extends Component {
const bulkSelect = permsState.bulkSelect;
const currentInputSelection = bulkSelect.filter(e => e === role)
.length ? (
-
- ) : (
-
- );
+
+ ) : (
+
+ );
_permissionsRowHtml.push(
@@ -765,164 +772,164 @@ class Permissions extends Component {
const setOptions =
insertState && insertState.localSet && insertState.localSet.length > 0
? insertState.localSet.map((s, i) => {
- return (
-
-
+
-
-
+ {columns && columns.length > 0
+ ? columns.map((c, key) => (
+
+ {c.column_name}
+
+ ))
+ : null}
+
+
+
-
-
+ static
+ from session variable
+
+
+
- {setConfigValueType(s.value) === 'session' ? (
-
- X-Hasura-
-
+ {setConfigValueType(s.value) === 'session' ? (
+
+ X-Hasura-
+ this.onSetValueBlur(e, i, null)}
+ data-index-id={i}
+ data-prefix-val={X_HASURA_CONST}
+ disabled={disableInput}
+ />
+
+ ) : (
+ this.onSetValueBlur(e, i, null)}
- data-index-id={i}
+ onBlur={this.onSetValueBlur}
+ indexId={i}
data-prefix-val={X_HASURA_CONST}
disabled={disableInput}
/>
-
- ) : (
-
- )}
-
- {setConfigValueType(s.value) === 'session' ? (
-
+ {setConfigValueType(s.value) === 'session' ? (
+
+ }
+ >
e.g. X-Hasura-User-Id
-
- ) : (
-
+ ) : (
+
+ }
+ >
e.g. false, 1, some-text
-
- )}
- {i !== insertState.localSet.length - 1 ? (
-
+ )}
+ {i !== insertState.localSet.length - 1 ? (
+
-
-
- ) : (
-
+
+
+ ) : (
+
- )}
-
- );
- })
+ }
+ />
+ )}
+
+ );
+ })
: null;
return (
@@ -1056,13 +1063,15 @@ class Permissions extends Component {
const getColumnSection = (tableSchema, permsState) => {
let _columnSection = '';
const query = permsState.query;
-
- if (queriesWithPermColumns.indexOf(query) !== -1) {
+ if (
+ getQueriesWithPermColumns(
+ semverCheck('insertPermRestrictColumns', this.props.serverVersion)
+ ).indexOf(query) !== -1
+ ) {
const dispatchToggleAllColumns = () => {
const allColumns = tableSchema.columns.map(c => c.column_name);
dispatch(permToggleAllColumns(allColumns));
};
-
_columnSection = (
diff --git a/console/src/helpers/semver.js b/console/src/helpers/semver.js
index f78946847682f..5bfed163d26e4 100644
--- a/console/src/helpers/semver.js
+++ b/console/src/helpers/semver.js
@@ -9,6 +9,7 @@ const componentsSemver = {
supportColumnChangeTrigger: '1.0.0-alpha26',
analyzeApiChange: '1.0.0-alpha26',
insertPrefix: '1.0.0-alpha26',
+ insertPermRestrictColumns: '1.0.0-alpha28',
};
const getPreRelease = version => {
diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs b/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs
index ac85f4cfbf15d..d967e51f414a4 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs
@@ -504,8 +504,7 @@ resolveInsCtx tn = do
InsCtx view colInfos setVals relInfoMap <-
onNothing (Map.lookup tn ctxMap) $
throw500 $ "table " <> tn <<> " not found"
- let defValMap = Map.fromList $ flip zip (repeat $ S.SEUnsafe "DEFAULT") $
- map pgiName colInfos
+ let defValMap = S.mkColDefValMap $ map pgiName colInfos
defValWithSet = Map.union setVals defValMap
return (view, colInfos, defValWithSet, relInfoMap)
diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs b/server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs
index 97220546f5fba..9ee340ba3e56e 100644
--- a/server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs
+++ b/server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs
@@ -11,7 +11,6 @@ module Hasura.GraphQL.Resolve.Mutation
import Hasura.Prelude
-import qualified Data.HashMap.Strict as Map
import qualified Data.HashMap.Strict.InsOrd as OMap
import qualified Language.GraphQL.Draft.Syntax as G
@@ -43,15 +42,14 @@ convertMutResp ty selSet =
convertRowObj
:: (MonadError QErr m, MonadState PrepArgs m)
- => InsSetCols -> AnnGValue
+ => AnnGValue
-> m [(PGCol, S.SQLExp)]
-convertRowObj setVals val =
- flip withObject val $ \_ obj -> do
- inpVals <- forM (OMap.toList obj) $ \(k, v) -> do
+convertRowObj val =
+ flip withObject val $ \_ obj ->
+ forM (OMap.toList obj) $ \(k, v) -> do
prepExpM <- asPGColValM v >>= mapM prepare
let prepExp = fromMaybe (S.SEUnsafe "NULL") prepExpM
return (PGCol $ G.unName k, prepExp)
- return $ Map.toList setVals <> inpVals
type ApplySQLOp = (PGCol, S.SQLExp) -> S.SQLExp
@@ -98,7 +96,7 @@ convertUpdate
-> Convert RespTx
convertUpdate tn filterExp fld = do
-- a set expression is same as a row object
- setExpM <- withArgM args "_set" $ convertRowObj Map.empty
+ setExpM <- withArgM args "_set" convertRowObj
-- where bool expression to filter column
whereExp <- withArg args "where" $ convertBoolExp tn
-- increment operator on integer columns
diff --git a/server/src-lib/Hasura/RQL/DDL/Permission.hs b/server/src-lib/Hasura/RQL/DDL/Permission.hs
index 338c56098233c..72d75862964ed 100644
--- a/server/src-lib/Hasura/RQL/DDL/Permission.hs
+++ b/server/src-lib/Hasura/RQL/DDL/Permission.hs
@@ -74,6 +74,7 @@ data InsPerm
{ icCheck :: !BoolExp
, icAllowUpsert :: !(Maybe Bool)
, icSet :: !(Maybe Object)
+ , icColumns :: !(Maybe PermColSpec)
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''InsPerm)
@@ -109,7 +110,7 @@ buildInsPermInfo
=> TableInfo
-> PermDef InsPerm
-> m InsPermInfo
-buildInsPermInfo tabInfo (PermDef rn (InsPerm chk upsrt set) _) = withPathK "permission" $ do
+buildInsPermInfo tabInfo (PermDef rn (InsPerm chk upsrt set mCols) _) = withPathK "permission" $ do
(be, beDeps) <- withPathK "check" $
procBoolExp tn fieldInfoMap (S.QualVar "NEW") chk
let deps = mkParentDep tn : beDeps
@@ -125,11 +126,17 @@ buildInsPermInfo tabInfo (PermDef rn (InsPerm chk upsrt set) _) = withPathK "per
return (pgCol, sqlExp)
let setHdrs = mapMaybe (fetchHdr . snd) (HM.toList setObj)
reqHdrs = fltrHeaders `union` setHdrs
- return $ InsPermInfo vn be allowUpsrt setColsSQL deps reqHdrs
+ preSetCols = HM.union setColsSQL nonInsColVals
+ return $ InsPermInfo vn be allowUpsrt preSetCols deps reqHdrs
where
fieldInfoMap = tiFieldInfoMap tabInfo
tn = tiName tabInfo
vn = buildViewName tn rn PTInsert
+ allCols = map pgiName $ getCols fieldInfoMap
+ nonInsCols = case mCols of
+ Nothing -> []
+ Just cols -> (\\) allCols $ convColSpec fieldInfoMap cols
+ nonInsColVals = S.mkColDefValMap nonInsCols
fetchHdr (String t) = bool Nothing (Just $ T.toLower t)
$ isUserVar t
diff --git a/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs b/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs
index b089847e897e8..438837de5dc11 100644
--- a/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs
+++ b/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs
@@ -47,9 +47,7 @@ instance ToJSON PermColSpec where
convColSpec :: FieldInfoMap -> PermColSpec -> [PGCol]
convColSpec _ (PCCols cols) = cols
-convColSpec cim PCStar =
- map pgiName $ fst $ partitionEithers $
- map fieldInfoToEither $ M.elems cim
+convColSpec cim PCStar = map pgiName $ getCols cim
assertPermNotDefined
:: (MonadError QErr m)
diff --git a/server/src-lib/Hasura/RQL/DML/Insert.hs b/server/src-lib/Hasura/RQL/DML/Insert.hs
index 1bee82e82a4a7..ea3e39b4c0722 100644
--- a/server/src-lib/Hasura/RQL/DML/Insert.hs
+++ b/server/src-lib/Hasura/RQL/DML/Insert.hs
@@ -86,9 +86,11 @@ convObj
-> InsObj
-> m ([PGCol], [S.SQLExp])
convObj prepFn defInsVals setInsVals fieldInfoMap insObj = do
- inpInsVals <- flip HM.traverseWithKey reqInsObj $ \c val -> do
+ inpInsVals <- flip HM.traverseWithKey insObj $ \c val -> do
let relWhenPGErr = "relationships can't be inserted"
colType <- askPGType fieldInfoMap c relWhenPGErr
+ -- if column has predefined value then throw error
+ when (c `elem` preSetCols) $ throwNotInsErr c
-- Encode aeson's value into prepared value
withPathK (getPGColTxt c) $ prepFn colType val
let insVals = HM.union setInsVals inpInsVals
@@ -97,7 +99,12 @@ convObj prepFn defInsVals setInsVals fieldInfoMap insObj = do
return (inpCols, sqlExps)
where
- reqInsObj = HM.difference insObj setInsVals
+ preSetCols = HM.keys setInsVals
+
+ throwNotInsErr c = do
+ role <- userRole <$> askUserInfo
+ throw400 NotSupported $ "column " <> c <<> " is not insertable"
+ <> " for role " <>> role
buildConflictClause
:: (P1C m)
diff --git a/server/src-lib/Hasura/SQL/DML.hs b/server/src-lib/Hasura/SQL/DML.hs
index 182eddf85686d..59490ec37bbc9 100644
--- a/server/src-lib/Hasura/SQL/DML.hs
+++ b/server/src-lib/Hasura/SQL/DML.hs
@@ -12,6 +12,7 @@ import Hasura.SQL.Types
import Data.String (fromString)
import Language.Haskell.TH.Syntax (Lift)
+import qualified Data.HashMap.Strict as HM
import qualified Data.Text.Extended as T
import qualified Text.Builder as TB
@@ -318,6 +319,10 @@ mkSQLOpExp
-> SQLExp -- result
mkSQLOpExp op lhs rhs = SEOpApp op [lhs, rhs]
+mkColDefValMap :: [PGCol] -> HM.HashMap PGCol SQLExp
+mkColDefValMap cols =
+ HM.fromList $ zip cols (repeat $ SEUnsafe "DEFAULT")
+
handleIfNull :: SQLExp -> SQLExp -> SQLExp
handleIfNull l e = SEFnApp "coalesce" [e, l] Nothing
diff --git a/server/tests-py/queries/graphql_mutation/insert/permissions/resident_infant.yaml b/server/tests-py/queries/graphql_mutation/insert/permissions/resident_infant.yaml
new file mode 100644
index 0000000000000..0e942b37e58ad
--- /dev/null
+++ b/server/tests-py/queries/graphql_mutation/insert/permissions/resident_infant.yaml
@@ -0,0 +1,35 @@
+description: Insert into resident table only with insertable columns
+url: /v1alpha1/graphql
+status: 200
+headers:
+ X-Hasura-Role: infant
+ X-Hasura-Infant-Id: '1'
+ X-Hasura-Infant-Name: 'Bittu'
+response:
+ data:
+ insert_resident:
+ affected_rows: 1
+ returning:
+ - id: 1
+ name: Bittu
+ age: 3
+ is_user: false
+query:
+ query: |
+ mutation {
+ insert_resident(
+ objects: [
+ {
+ age: 3
+ }
+ ]
+ ){
+ affected_rows
+ returning{
+ id
+ name
+ age
+ is_user
+ }
+ }
+ }
diff --git a/server/tests-py/queries/graphql_mutation/insert/permissions/resident_infant_fail.yaml b/server/tests-py/queries/graphql_mutation/insert/permissions/resident_infant_fail.yaml
new file mode 100644
index 0000000000000..a8b12d33a93be
--- /dev/null
+++ b/server/tests-py/queries/graphql_mutation/insert/permissions/resident_infant_fail.yaml
@@ -0,0 +1,27 @@
+description: Insert into resident table with non insertable columns (Error)
+url: /v1alpha1/graphql
+status: 400
+headers:
+ X-Hasura-Role: infant
+ X-Hasura-Infant-Id: '1'
+ X-Hasura-Infant-Name: 'Bittu'
+query:
+ query: |
+ mutation {
+ insert_resident(
+ objects: [
+ {
+ age: 3
+ is_user: true
+ }
+ ]
+ ){
+ affected_rows
+ returning{
+ id
+ name
+ age
+ is_user
+ }
+ }
+ }
diff --git a/server/tests-py/queries/graphql_mutation/insert/permissions/setup.yaml b/server/tests-py/queries/graphql_mutation/insert/permissions/setup.yaml
index 550600f992412..1154044035ddd 100644
--- a/server/tests-py/queries/graphql_mutation/insert/permissions/setup.yaml
+++ b/server/tests-py/queries/graphql_mutation/insert/permissions/setup.yaml
@@ -222,3 +222,33 @@ args:
- is_user
filter:
id: X-Hasura-Resident-Id
+
+#Create insert permission for infant on resident
+- type: create_insert_permission
+ args:
+ table: resident
+ role: infant
+ permission:
+ check:
+ id: X-Hasura-Infant-Id
+ allow_upsert: false
+ set:
+ name: X-Hasura-Infant-Name
+ id: X-Hasura-Infant-Id
+ columns:
+ - age
+
+#Create select permission for infant on resident
+- type: create_select_permission
+ args:
+ table: resident
+ role: infant
+ permission:
+ columns:
+ - id
+ - name
+ - age
+ - is_user
+ filter:
+ id: X-Hasura-Infant-Id
+
diff --git a/server/tests-py/test_graphql_mutations.py b/server/tests-py/test_graphql_mutations.py
index 886ef7172ed87..354602ac68132 100644
--- a/server/tests-py/test_graphql_mutations.py
+++ b/server/tests-py/test_graphql_mutations.py
@@ -117,6 +117,12 @@ def test_company_user_role_insert_on_conflict(self, hge_ctx):
def test_resident_user_role_insert(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/resident_user.yaml")
+ def test_resident_infant_role_insert(self, hge_ctx):
+ check_query_f(hge_ctx, self.dir() + "/resident_infant.yaml")
+
+ def test_resident_infant_role_insert_fail(self, hge_ctx):
+ check_query_f(hge_ctx, self.dir() + "/resident_infant_fail.yaml")
+
@classmethod
def dir(cls):
return "queries/graphql_mutation/insert/permissions"
|