2018-06-27 16:11:32 +03:00
|
|
|
module Hasura.RQL.DDL.Permission
|
|
|
|
( CreatePerm
|
2018-12-13 10:26:15 +03:00
|
|
|
, runCreatePerm
|
2018-06-27 16:11:32 +03:00
|
|
|
, PermDef(..)
|
|
|
|
|
|
|
|
, InsPerm(..)
|
|
|
|
, InsPermDef
|
|
|
|
, buildInsPermInfo
|
|
|
|
|
|
|
|
, SelPerm(..)
|
|
|
|
, SelPermDef
|
|
|
|
, buildSelPermInfo
|
|
|
|
|
|
|
|
, UpdPerm(..)
|
|
|
|
, UpdPermDef
|
|
|
|
, buildUpdPermInfo
|
|
|
|
|
|
|
|
, DelPerm(..)
|
|
|
|
, DelPermDef
|
|
|
|
, buildDelPermInfo
|
|
|
|
|
|
|
|
, IsPerm(..)
|
2018-12-13 10:26:15 +03:00
|
|
|
|
|
|
|
, DropPerm
|
|
|
|
, runDropPerm
|
2020-12-08 17:22:31 +03:00
|
|
|
, dropPermissionInMetadata
|
2018-12-13 10:26:15 +03:00
|
|
|
|
|
|
|
, SetPermComment(..)
|
|
|
|
, runSetPermComment
|
2018-06-27 16:11:32 +03:00
|
|
|
) where
|
|
|
|
|
2020-08-27 19:36:39 +03:00
|
|
|
import Hasura.Prelude
|
|
|
|
|
2020-10-27 16:53:49 +03:00
|
|
|
import qualified Data.HashMap.Strict as HM
|
2020-12-08 17:22:31 +03:00
|
|
|
import qualified Data.HashMap.Strict.InsOrd as OMap
|
2020-10-27 16:53:49 +03:00
|
|
|
import qualified Data.HashSet as HS
|
|
|
|
|
2021-07-17 00:18:58 +03:00
|
|
|
import Control.Lens ((.~), (^?))
|
2020-10-27 16:53:49 +03:00
|
|
|
import Data.Aeson
|
2021-06-09 22:42:37 +03:00
|
|
|
import Data.Kind (Type)
|
2020-10-21 19:35:06 +03:00
|
|
|
import Data.Text.Extended
|
2020-10-27 16:53:49 +03:00
|
|
|
|
2021-03-15 16:02:58 +03:00
|
|
|
import qualified Hasura.SQL.AnyBackend as AB
|
|
|
|
|
2021-05-11 18:18:31 +03:00
|
|
|
import Hasura.Base.Error
|
2019-03-18 19:22:21 +03:00
|
|
|
import Hasura.EncJSON
|
2018-06-27 16:11:32 +03:00
|
|
|
import Hasura.RQL.DDL.Permission.Internal
|
2019-10-18 11:29:47 +03:00
|
|
|
import Hasura.RQL.DML.Internal hiding (askPermInfo)
|
2018-12-15 19:10:29 +03:00
|
|
|
import Hasura.RQL.Types
|
2020-12-28 15:56:00 +03:00
|
|
|
import Hasura.SQL.Types
|
2021-01-19 22:14:42 +03:00
|
|
|
import Hasura.Session
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-04-24 12:10:53 +03:00
|
|
|
{- Note [Backend only permissions]
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
As of writing this note, Hasura permission system is meant to be used by the
|
|
|
|
frontend. After introducing "Actions", the webhook handlers now can make GraphQL
|
|
|
|
mutations to the server with some backend logic. These mutations shouldn't be
|
|
|
|
exposed to frontend for any user since they'll bypass the business logic.
|
|
|
|
|
|
|
|
For example:-
|
|
|
|
|
|
|
|
We've a table named "user" and it has a "email" column. We need to validate the
|
|
|
|
email address. So we define an action "create_user" and it expects the same inputs
|
|
|
|
as "insert_user" mutation (generated by Hasura). Now, a role has permission for both
|
|
|
|
actions and insert operation on the table. If the insert permission is not marked
|
|
|
|
as "backend_only: true" then it visible to the frontend client along with "creat_user".
|
|
|
|
|
|
|
|
Backend only permissions adds an additional privilege to Hasura generated operations.
|
|
|
|
Those are accessable only if the request is made with `x-hasura-admin-secret`
|
|
|
|
(if authorization is configured), `x-hasura-use-backend-only-permissions`
|
|
|
|
(value must be set to "true"), `x-hasura-role` to identify the role and other
|
|
|
|
required session variables.
|
|
|
|
|
|
|
|
backend_only `x-hasura-admin-secret` `x-hasura-use-backend-only-permissions` Result
|
|
|
|
------------ --------------------- ------------------------------------- ------
|
|
|
|
FALSE ANY ANY Mutation is always visible
|
|
|
|
TRUE FALSE ANY Mutation is always hidden
|
|
|
|
TRUE TRUE (OR NOT-SET) FALSE Mutation is hidden
|
|
|
|
TRUE TRUE (OR NOT-SET) TRUE Mutation is shown
|
|
|
|
-}
|
|
|
|
|
2019-02-11 15:45:30 +03:00
|
|
|
procSetObj
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall b m
|
|
|
|
. (QErrM m, BackendMetadata b)
|
2020-12-28 15:56:00 +03:00
|
|
|
=> SourceName
|
2021-02-14 09:07:52 +03:00
|
|
|
-> TableName b
|
|
|
|
-> FieldInfoMap (FieldInfo b)
|
|
|
|
-> Maybe (ColumnValues b Value)
|
|
|
|
-> m (PreSetColsPartial b, [Text], [SchemaDependency])
|
2020-12-28 15:56:00 +03:00
|
|
|
procSetObj source tn fieldInfoMap mObj = do
|
2019-08-17 00:35:22 +03:00
|
|
|
(setColTups, deps) <- withPathK "set" $
|
|
|
|
fmap unzip $ forM (HM.toList setObj) $ \(pgCol, val) -> do
|
2021-02-14 09:07:52 +03:00
|
|
|
ty <- askColumnType fieldInfoMap pgCol $
|
2019-02-11 15:45:30 +03:00
|
|
|
"column " <> pgCol <<> " not found in table " <>> tn
|
2021-02-14 09:07:52 +03:00
|
|
|
sqlExp <- parseCollectableType (CollectableTypeScalar ty) val
|
2021-04-22 00:44:37 +03:00
|
|
|
let dep = mkColDep @b (getDepReason sqlExp) source tn pgCol
|
2019-08-17 00:35:22 +03:00
|
|
|
return ((pgCol, sqlExp), dep)
|
|
|
|
return (HM.fromList setColTups, depHeaders, deps)
|
2019-02-11 15:45:30 +03:00
|
|
|
where
|
|
|
|
setObj = fromMaybe mempty mObj
|
2021-02-14 09:07:52 +03:00
|
|
|
depHeaders = getDepHeadersFromVal $ Object $ mapKeys toTxt setObj
|
2019-02-11 15:45:30 +03:00
|
|
|
|
2019-08-17 00:35:22 +03:00
|
|
|
getDepReason = bool DRSessionVariable DROnType . isStaticValue
|
|
|
|
|
2021-06-09 22:42:37 +03:00
|
|
|
class IsPerm a where
|
|
|
|
type PermInfo a = (r :: BackendType -> Type) | r -> a
|
2020-11-12 12:25:48 +03:00
|
|
|
|
|
|
|
permAccessor
|
2021-06-09 22:42:37 +03:00
|
|
|
:: (ToJSON (a b), BackendMetadata b)
|
|
|
|
=> PermAccessor b (PermInfo a b)
|
2020-11-12 12:25:48 +03:00
|
|
|
|
|
|
|
buildPermInfo
|
2021-06-09 22:42:37 +03:00
|
|
|
:: (ToJSON (a b), BackendMetadata b, QErrM m, TableCoreInfoRM b m)
|
2020-12-28 15:56:00 +03:00
|
|
|
=> SourceName
|
2021-02-14 09:07:52 +03:00
|
|
|
-> TableName b
|
|
|
|
-> FieldInfoMap (FieldInfo b)
|
2021-06-09 22:42:37 +03:00
|
|
|
-> PermDef (a b)
|
|
|
|
-> m (WithDeps (PermInfo a b))
|
2020-11-12 12:25:48 +03:00
|
|
|
|
|
|
|
getPermAcc1
|
2021-06-09 22:42:37 +03:00
|
|
|
:: (ToJSON (a b), BackendMetadata b)
|
|
|
|
=> PermDef (a b)
|
|
|
|
-> PermAccessor b (PermInfo a b)
|
2020-11-12 12:25:48 +03:00
|
|
|
getPermAcc1 _ = permAccessor
|
|
|
|
|
|
|
|
getPermAcc2
|
2021-06-09 22:42:37 +03:00
|
|
|
:: (ToJSON (a b), BackendMetadata b)
|
|
|
|
=> DropPerm a b
|
|
|
|
-> PermAccessor b (PermInfo a b)
|
2020-11-12 12:25:48 +03:00
|
|
|
getPermAcc2 _ = permAccessor
|
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
addPermToMetadata
|
2021-06-09 22:42:37 +03:00
|
|
|
:: (ToJSON (a b), BackendMetadata b)
|
|
|
|
=> PermDef (a b)
|
|
|
|
-> TableMetadata b
|
|
|
|
-> TableMetadata b
|
2020-11-12 12:25:48 +03:00
|
|
|
|
2021-07-17 00:18:58 +03:00
|
|
|
doesPermissionExistInMetadata
|
|
|
|
:: TableMetadata b
|
|
|
|
-> RoleName
|
|
|
|
-> PermType
|
|
|
|
-> Bool
|
|
|
|
doesPermissionExistInMetadata tableMetadata roleName = \case
|
|
|
|
-- TODO: lot of repetition below, any way to simplify this?
|
|
|
|
PTInsert -> isJust $ tableMetadata ^? tmInsertPermissions.ix roleName
|
|
|
|
PTSelect -> isJust $ tableMetadata ^? tmSelectPermissions.ix roleName
|
|
|
|
PTUpdate -> isJust $ tableMetadata ^? tmUpdatePermissions.ix roleName
|
|
|
|
PTDelete -> isJust $ tableMetadata ^? tmDeletePermissions.ix roleName
|
|
|
|
|
2020-11-12 12:25:48 +03:00
|
|
|
runCreatePerm
|
2021-03-15 16:02:58 +03:00
|
|
|
:: forall m b a
|
2021-06-09 22:42:37 +03:00
|
|
|
. (ToJSON (a b), IsPerm a, UserInfoM m, CacheRWM m, MonadError QErr m, MetadataM m, BackendMetadata b)
|
|
|
|
=> CreatePerm a b -> m EncJSON
|
2021-07-17 00:18:58 +03:00
|
|
|
runCreatePerm (CreatePerm (WithTable source tableName permissionDefn)) = do
|
|
|
|
tableMetadata <- askTableMetadata @b source tableName
|
|
|
|
let permAcc = getPermAcc1 permissionDefn
|
|
|
|
permissionType = permAccToType permAcc
|
|
|
|
ptText = permTypeToCode permissionType
|
|
|
|
role = _pdRole permissionDefn
|
2021-03-15 16:02:58 +03:00
|
|
|
metadataObject = MOSourceObjId source
|
|
|
|
$ AB.mkAnyBackend
|
2021-07-17 00:18:58 +03:00
|
|
|
$ SMOTableObj @b tableName
|
|
|
|
$ MTOPerm role permissionType
|
|
|
|
|
|
|
|
-- NOTE: we check if a permission exists for a `(table, role)` entity in the metadata
|
|
|
|
-- and not in the `RolePermInfoMap b` because there may exist a permission for the `role`
|
|
|
|
-- which is an inherited one, so we check it in the metadata directly
|
|
|
|
|
|
|
|
-- The metadata will not contain the permissions for the admin role,
|
|
|
|
-- because the graphql-engine automatically creates the role and it's
|
|
|
|
-- assumed that the admin role is an implicit role of the graphql-engine.
|
|
|
|
when (doesPermissionExistInMetadata tableMetadata role permissionType || role == adminRoleName) $ throw400 AlreadyExists $
|
|
|
|
ptText <> " permission already defined on table " <> tableName <<> " with role " <>> role
|
2020-12-08 17:22:31 +03:00
|
|
|
buildSchemaCacheFor metadataObject
|
|
|
|
$ MetadataModifier
|
2021-07-17 00:18:58 +03:00
|
|
|
$ tableMetadataSetter @b source tableName %~ addPermToMetadata permissionDefn
|
2020-11-12 12:25:48 +03:00
|
|
|
pure successMsg
|
|
|
|
|
|
|
|
runDropPerm
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall b a m
|
2021-06-09 22:42:37 +03:00
|
|
|
. (ToJSON (a b), IsPerm a, UserInfoM m, CacheRWM m, MonadError QErr m, MetadataM m, BackendMetadata b)
|
|
|
|
=> DropPerm a b
|
2021-04-22 00:44:37 +03:00
|
|
|
-> m EncJSON
|
2020-12-28 15:56:00 +03:00
|
|
|
runDropPerm dp@(DropPerm source table role) = do
|
2021-07-17 00:18:58 +03:00
|
|
|
tableMetadata <- askTableMetadata @b source table
|
2020-12-08 17:22:31 +03:00
|
|
|
let permType = permAccToType $ getPermAcc2 dp
|
2021-07-17 00:18:58 +03:00
|
|
|
unless (doesPermissionExistInMetadata tableMetadata role permType) $ do
|
|
|
|
let errMsg = mconcat
|
|
|
|
[ permTypeToCode permType <> " permission on " <>> table
|
|
|
|
, " for role " <>> role
|
|
|
|
, " does not exist"
|
|
|
|
]
|
|
|
|
throw400 PermissionDenied errMsg
|
2020-12-08 17:22:31 +03:00
|
|
|
withNewInconsistentObjsCheck
|
|
|
|
$ buildSchemaCache
|
|
|
|
$ MetadataModifier
|
2021-04-22 00:44:37 +03:00
|
|
|
$ tableMetadataSetter @b source table %~ dropPermissionInMetadata role permType
|
2020-11-12 12:25:48 +03:00
|
|
|
return successMsg
|
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
dropPermissionInMetadata
|
2021-02-14 09:07:52 +03:00
|
|
|
:: RoleName -> PermType -> TableMetadata b -> TableMetadata b
|
2020-12-08 17:22:31 +03:00
|
|
|
dropPermissionInMetadata rn = \case
|
|
|
|
PTInsert -> tmInsertPermissions %~ OMap.delete rn
|
|
|
|
PTSelect -> tmSelectPermissions %~ OMap.delete rn
|
|
|
|
PTDelete -> tmDeletePermissions %~ OMap.delete rn
|
|
|
|
PTUpdate -> tmUpdatePermissions %~ OMap.delete rn
|
|
|
|
|
2021-06-09 22:42:37 +03:00
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
buildInsPermInfo
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall b m
|
|
|
|
. (QErrM m, TableCoreInfoRM b m, BackendMetadata b)
|
2020-12-28 15:56:00 +03:00
|
|
|
=> SourceName
|
2021-02-14 09:07:52 +03:00
|
|
|
-> TableName b
|
|
|
|
-> FieldInfoMap (FieldInfo b)
|
|
|
|
-> PermDef (InsPerm b)
|
|
|
|
-> m (WithDeps (InsPermInfo b))
|
2020-12-28 15:56:00 +03:00
|
|
|
buildInsPermInfo source tn fieldInfoMap (PermDef _rn (InsPerm checkCond set mCols mBackendOnly) _) =
|
2019-04-17 12:48:41 +03:00
|
|
|
withPathK "permission" $ do
|
2020-12-28 15:56:00 +03:00
|
|
|
(be, beDeps) <- withPathK "check" $ procBoolExp source tn fieldInfoMap checkCond
|
|
|
|
(setColsSQL, setHdrs, setColDeps) <- procSetObj source tn fieldInfoMap set
|
2019-12-15 19:07:08 +03:00
|
|
|
void $ withPathK "columns" $ indexedForM insCols $ \col ->
|
2021-02-14 09:07:52 +03:00
|
|
|
askColumnType fieldInfoMap col ""
|
2020-02-13 10:38:49 +03:00
|
|
|
let fltrHeaders = getDependentHeaders checkCond
|
2019-12-15 19:07:08 +03:00
|
|
|
reqHdrs = fltrHeaders `union` setHdrs
|
2021-04-22 00:44:37 +03:00
|
|
|
insColDeps = map (mkColDep @b DRUntyped source tn) insCols
|
|
|
|
deps = mkParentDep @b source tn : beDeps ++ setColDeps ++ insColDeps
|
2019-12-15 19:07:08 +03:00
|
|
|
insColsWithoutPresets = insCols \\ HM.keys setColsSQL
|
2020-04-24 12:10:53 +03:00
|
|
|
return (InsPermInfo (HS.fromList insColsWithoutPresets) be setColsSQL backendOnly reqHdrs, deps)
|
2018-06-27 16:11:32 +03:00
|
|
|
where
|
2020-10-28 19:40:33 +03:00
|
|
|
backendOnly = Just True == mBackendOnly
|
2019-09-19 07:47:36 +03:00
|
|
|
allCols = map pgiColumn $ getCols fieldInfoMap
|
2020-10-28 19:40:33 +03:00
|
|
|
insCols = maybe allCols (convColSpec fieldInfoMap) mCols
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-06-09 22:42:37 +03:00
|
|
|
instance IsPerm InsPerm where
|
|
|
|
type PermInfo InsPerm = InsPermInfo
|
2018-06-27 16:11:32 +03:00
|
|
|
permAccessor = PAInsert
|
|
|
|
buildPermInfo = buildInsPermInfo
|
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
addPermToMetadata permDef =
|
|
|
|
tmInsertPermissions %~ OMap.insert (_pdRole permDef) permDef
|
|
|
|
|
2021-06-09 22:42:37 +03:00
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
buildSelPermInfo
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall b m
|
|
|
|
. (QErrM m, TableCoreInfoRM b m, BackendMetadata b)
|
2020-12-28 15:56:00 +03:00
|
|
|
=> SourceName
|
2021-02-14 09:07:52 +03:00
|
|
|
-> TableName b
|
|
|
|
-> FieldInfoMap (FieldInfo b)
|
|
|
|
-> SelPerm b
|
|
|
|
-> m (WithDeps (SelPermInfo b))
|
2020-12-28 15:56:00 +03:00
|
|
|
buildSelPermInfo source tn fieldInfoMap sp = withPathK "permission" $ do
|
2018-06-27 16:11:32 +03:00
|
|
|
let pgCols = convColSpec fieldInfoMap $ spColumns sp
|
|
|
|
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
(boolExp, boolExpDeps) <- withPathK "filter" $
|
2020-12-28 15:56:00 +03:00
|
|
|
procBoolExp source tn fieldInfoMap $ spFilter sp
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
-- check if the columns exist
|
|
|
|
void $ withPathK "columns" $ indexedForM pgCols $ \pgCol ->
|
2021-02-14 09:07:52 +03:00
|
|
|
askColumnType fieldInfoMap pgCol autoInferredErr
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-10-18 11:29:47 +03:00
|
|
|
-- validate computed fields
|
2019-11-07 17:39:48 +03:00
|
|
|
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"
|
2019-10-18 11:29:47 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
let deps = mkParentDep @b source tn : boolExpDeps ++ map (mkColDep @b DRUntyped source tn) pgCols
|
|
|
|
++ map (mkComputedFieldDep @b DRUntyped source tn) scalarComputedFields
|
2018-06-27 16:11:32 +03:00
|
|
|
depHeaders = getDependentHeaders $ spFilter sp
|
2018-08-06 15:15:08 +03:00
|
|
|
mLimit = spLimit sp
|
|
|
|
|
|
|
|
withPathK "limit" $ mapM_ onlyPositiveInt mLimit
|
2018-06-27 16:11:32 +03:00
|
|
|
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
let pgColsWithFilter = HM.fromList $ map (, Nothing) pgCols
|
|
|
|
scalarComputedFieldsWithFilter = HS.toMap (HS.fromList scalarComputedFields) $> Nothing
|
|
|
|
|
|
|
|
let selPermInfo =
|
|
|
|
SelPermInfo pgColsWithFilter scalarComputedFieldsWithFilter boolExp mLimit allowAgg depHeaders
|
|
|
|
|
|
|
|
return ( selPermInfo, deps )
|
2018-06-27 16:11:32 +03:00
|
|
|
where
|
2019-10-18 11:29:47 +03:00
|
|
|
allowAgg = spAllowAggregations sp
|
|
|
|
computedFields = spComputedFields sp
|
2018-06-27 16:11:32 +03:00
|
|
|
autoInferredErr = "permissions for relationships are automatically inferred"
|
|
|
|
|
2021-06-09 22:42:37 +03:00
|
|
|
instance IsPerm SelPerm where
|
|
|
|
type PermInfo SelPerm = SelPermInfo
|
2018-06-27 16:11:32 +03:00
|
|
|
permAccessor = PASelect
|
2020-12-28 15:56:00 +03:00
|
|
|
buildPermInfo source tn fieldInfoMap (PermDef _ a _) =
|
|
|
|
buildSelPermInfo source tn fieldInfoMap a
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
addPermToMetadata permDef =
|
|
|
|
tmSelectPermissions %~ OMap.insert (_pdRole permDef) permDef
|
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
buildUpdPermInfo
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall b m
|
|
|
|
. (QErrM m, TableCoreInfoRM b m, BackendMetadata b)
|
2020-12-28 15:56:00 +03:00
|
|
|
=> SourceName
|
2021-02-14 09:07:52 +03:00
|
|
|
-> TableName b
|
|
|
|
-> FieldInfoMap (FieldInfo b)
|
|
|
|
-> UpdPerm b
|
|
|
|
-> m (WithDeps (UpdPermInfo b))
|
2020-12-28 15:56:00 +03:00
|
|
|
buildUpdPermInfo source tn fieldInfoMap (UpdPerm colSpec set fltr check) = do
|
2018-06-27 16:11:32 +03:00
|
|
|
(be, beDeps) <- withPathK "filter" $
|
2020-12-28 15:56:00 +03:00
|
|
|
procBoolExp source tn fieldInfoMap fltr
|
2020-04-24 12:10:53 +03:00
|
|
|
|
2020-12-28 15:56:00 +03:00
|
|
|
checkExpr <- traverse (withPathK "check" . procBoolExp source tn fieldInfoMap) check
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-12-28 15:56:00 +03:00
|
|
|
(setColsSQL, setHeaders, setColDeps) <- procSetObj source tn fieldInfoMap set
|
2019-02-11 15:45:30 +03:00
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
-- check if the columns exist
|
2019-06-21 14:04:21 +03:00
|
|
|
void $ withPathK "columns" $ indexedForM updCols $ \updCol ->
|
2021-02-14 09:07:52 +03:00
|
|
|
askColumnType fieldInfoMap updCol relInUpdErr
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
let updColDeps = map (mkColDep @b DRUntyped source tn) updCols
|
|
|
|
deps = mkParentDep @b source tn : beDeps ++ maybe [] snd checkExpr ++ updColDeps ++ setColDeps
|
2018-06-27 16:11:32 +03:00
|
|
|
depHeaders = getDependentHeaders fltr
|
2019-02-11 15:45:30 +03:00
|
|
|
reqHeaders = depHeaders `union` setHeaders
|
|
|
|
updColsWithoutPreSets = updCols \\ HM.keys setColsSQL
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-02-13 10:38:49 +03:00
|
|
|
return (UpdPermInfo (HS.fromList updColsWithoutPreSets) tn be (fst <$> checkExpr) setColsSQL reqHeaders, deps)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
where
|
|
|
|
updCols = convColSpec fieldInfoMap colSpec
|
|
|
|
relInUpdErr = "relationships can't be used in update"
|
|
|
|
|
2021-06-09 22:42:37 +03:00
|
|
|
instance IsPerm UpdPerm where
|
|
|
|
type PermInfo UpdPerm = UpdPermInfo
|
2018-06-27 16:11:32 +03:00
|
|
|
permAccessor = PAUpdate
|
2020-12-28 15:56:00 +03:00
|
|
|
buildPermInfo source tn fieldInfoMap (PermDef _ a _) =
|
|
|
|
buildUpdPermInfo source tn fieldInfoMap a
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
addPermToMetadata permDef =
|
|
|
|
tmUpdatePermissions %~ OMap.insert (_pdRole permDef) permDef
|
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
buildDelPermInfo
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall b m
|
|
|
|
. (QErrM m, TableCoreInfoRM b m, BackendMetadata b)
|
2020-12-28 15:56:00 +03:00
|
|
|
=> SourceName
|
2021-02-14 09:07:52 +03:00
|
|
|
-> TableName b
|
|
|
|
-> FieldInfoMap (FieldInfo b)
|
|
|
|
-> DelPerm b
|
|
|
|
-> m (WithDeps (DelPermInfo b))
|
2020-12-28 15:56:00 +03:00
|
|
|
buildDelPermInfo source tn fieldInfoMap (DelPerm fltr) = do
|
2018-06-27 16:11:32 +03:00
|
|
|
(be, beDeps) <- withPathK "filter" $
|
2020-12-28 15:56:00 +03:00
|
|
|
procBoolExp source tn fieldInfoMap fltr
|
2021-04-22 00:44:37 +03:00
|
|
|
let deps = mkParentDep @b source tn : beDeps
|
2018-06-27 16:11:32 +03:00
|
|
|
depHeaders = getDependentHeaders fltr
|
2018-11-16 15:40:23 +03:00
|
|
|
return (DelPermInfo tn be depHeaders, deps)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-06-09 22:42:37 +03:00
|
|
|
instance IsPerm DelPerm where
|
|
|
|
type PermInfo DelPerm = DelPermInfo
|
2018-06-27 16:11:32 +03:00
|
|
|
permAccessor = PADelete
|
2020-12-28 15:56:00 +03:00
|
|
|
buildPermInfo source tn fieldInfoMap (PermDef _ a _) =
|
|
|
|
buildDelPermInfo source tn fieldInfoMap a
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-12-08 17:22:31 +03:00
|
|
|
addPermToMetadata permDef =
|
|
|
|
tmDeletePermissions %~ OMap.insert (_pdRole permDef) permDef
|
|
|
|
|
2021-06-09 22:42:37 +03:00
|
|
|
|
2021-02-23 20:37:27 +03:00
|
|
|
data SetPermComment b
|
2018-06-27 16:11:32 +03:00
|
|
|
= SetPermComment
|
2020-12-28 15:56:00 +03:00
|
|
|
{ apSource :: !SourceName
|
2021-02-23 20:37:27 +03:00
|
|
|
, apTable :: !(TableName b)
|
2018-06-27 16:11:32 +03:00
|
|
|
, apRole :: !RoleName
|
|
|
|
, apPermission :: !PermType
|
2020-10-27 16:53:49 +03:00
|
|
|
, apComment :: !(Maybe Text)
|
2021-02-23 20:37:27 +03:00
|
|
|
} deriving (Generic)
|
|
|
|
deriving instance (Backend b) => Show (SetPermComment b)
|
|
|
|
deriving instance (Backend b) => Eq (SetPermComment b)
|
|
|
|
instance (Backend b) => ToJSON (SetPermComment b) where
|
|
|
|
toJSON = genericToJSON hasuraJSON
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-02-23 20:37:27 +03:00
|
|
|
instance (Backend b) => FromJSON (SetPermComment b) where
|
2020-12-28 15:56:00 +03:00
|
|
|
parseJSON = withObject "Object" $ \o ->
|
|
|
|
SetPermComment
|
|
|
|
<$> o .:? "source" .!= defaultSource
|
|
|
|
<*> o .: "table"
|
|
|
|
<*> o .: "role"
|
|
|
|
<*> o .: "permission"
|
|
|
|
<*> o .:? "comment"
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-12-13 10:26:15 +03:00
|
|
|
runSetPermComment
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall b m
|
2021-03-15 16:02:58 +03:00
|
|
|
. (QErrM m, CacheRWM m, MetadataM m, BackendMetadata b)
|
2021-04-22 00:44:37 +03:00
|
|
|
=> SetPermComment b
|
|
|
|
-> m EncJSON
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
runSetPermComment (SetPermComment source table roleName permType comment) = do
|
2021-04-22 00:44:37 +03:00
|
|
|
tableInfo <- askTabInfo @b source table
|
2020-12-28 15:56:00 +03:00
|
|
|
|
|
|
|
-- assert permission exists and return appropriate permission modifier
|
|
|
|
permModifier <- case permType of
|
|
|
|
PTInsert -> do
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
assertPermDefined roleName PAInsert tableInfo
|
|
|
|
pure $ tmInsertPermissions.ix roleName.pdComment .~ comment
|
2020-12-28 15:56:00 +03:00
|
|
|
PTSelect -> do
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
assertPermDefined roleName PASelect tableInfo
|
|
|
|
pure $ tmSelectPermissions.ix roleName.pdComment .~ comment
|
2020-12-28 15:56:00 +03:00
|
|
|
PTUpdate -> do
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
assertPermDefined roleName PAUpdate tableInfo
|
|
|
|
pure $ tmUpdatePermissions.ix roleName.pdComment .~ comment
|
2020-12-28 15:56:00 +03:00
|
|
|
PTDelete -> do
|
[Preview] Inherited roles for postgres read queries
fixes #3868
docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de`
Note:
To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`.
Introduction
------------
This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`.
How are select permissions of different roles are combined?
------------------------------------------------------------
A select permission includes 5 things:
1. Columns accessible to the role
2. Row selection filter
3. Limit
4. Allow aggregation
5. Scalar computed fields accessible to the role
Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`.
Let's say the following GraphQL query is queried with the `combined_roles` role.
```graphql
query {
employees {
address
phone
}
}
```
This will translate to the following SQL query:
```sql
select
(case when (P1 or P2) then address else null end) as address,
(case when P2 then phone else null end) as phone
from employee
where (P1 or P2)
```
The other parameters of the select permission will be combined in the following manner:
1. Limit - Minimum of the limits will be the limit of the inherited role
2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation
3. Scalar computed fields - same as table column fields, as in the above example
APIs for inherited roles:
----------------------
1. `add_inherited_role`
`add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments
`role_name`: the name of the inherited role to be added (String)
`role_set`: list of roles that need to be combined (Array of Strings)
Example:
```json
{
"type": "add_inherited_role",
"args": {
"role_name":"combined_user",
"role_set":[
"user",
"user1"
]
}
}
```
After adding the inherited role, the inherited role can be used like single roles like earlier
Note:
An inherited role can only be created with non-inherited/singular roles.
2. `drop_inherited_role`
The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument:
`role_name`: name of the inherited role to be dropped
Example:
```json
{
"type": "drop_inherited_role",
"args": {
"role_name":"combined_user"
}
}
```
Metadata
---------
The derived roles metadata will be included under the `experimental_features` key while exporting the metadata.
```json
{
"experimental_features": {
"derived_roles": [
{
"role_name": "manager_is_employee_too",
"role_set": [
"employee",
"manager"
]
}
]
}
}
```
Scope
------
Only postgres queries and subscriptions are supported in this PR.
Important points:
-----------------
1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done.
TODOs
-------
- [ ] Tests
- [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features
- [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?)
- [ ] Introspection test with a inherited role (nullability changes in a inherited role)
- [ ] Docs
- [ ] Changelog
Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com>
GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
|
|
|
assertPermDefined roleName PADelete tableInfo
|
|
|
|
pure $ tmDeletePermissions.ix roleName.pdComment .~ comment
|
2020-12-28 15:56:00 +03:00
|
|
|
|
2021-03-15 16:02:58 +03:00
|
|
|
let metadataObject = MOSourceObjId source
|
|
|
|
$ AB.mkAnyBackend
|
2021-04-22 00:44:37 +03:00
|
|
|
$ SMOTableObj @b table
|
2021-03-15 16:02:58 +03:00
|
|
|
$ MTOPerm roleName permType
|
2020-12-28 15:56:00 +03:00
|
|
|
buildSchemaCacheFor metadataObject
|
|
|
|
$ MetadataModifier
|
2021-04-22 00:44:37 +03:00
|
|
|
$ tableMetadataSetter @b source table %~ permModifier
|
2020-12-28 15:56:00 +03:00
|
|
|
pure successMsg
|