2021-01-07 12:04:22 +03:00
|
|
|
-- | The RQL query ('/v2/query')
|
2021-05-21 05:46:58 +03:00
|
|
|
|
2021-01-07 12:04:22 +03:00
|
|
|
module Hasura.Server.API.V2Query where
|
|
|
|
|
2021-05-11 18:18:31 +03:00
|
|
|
import Hasura.Prelude
|
|
|
|
|
|
|
|
import qualified Data.Environment as Env
|
|
|
|
import qualified Network.HTTP.Client as HTTP
|
|
|
|
|
2021-04-16 16:26:11 +03:00
|
|
|
import Control.Monad.Trans.Control (MonadBaseControl)
|
2021-01-07 12:04:22 +03:00
|
|
|
import Data.Aeson
|
|
|
|
import Data.Aeson.Casing
|
|
|
|
import Data.Aeson.TH
|
|
|
|
|
2021-05-11 18:18:31 +03:00
|
|
|
import qualified Hasura.Backends.BigQuery.DDL.RunSQL as BigQuery
|
|
|
|
import qualified Hasura.Backends.MSSQL.DDL.RunSQL as MSSQL
|
|
|
|
import qualified Hasura.Tracing as Tracing
|
2021-01-07 12:04:22 +03:00
|
|
|
|
2021-05-11 18:18:31 +03:00
|
|
|
import Hasura.Base.Error
|
2021-01-07 12:04:22 +03:00
|
|
|
import Hasura.EncJSON
|
|
|
|
import Hasura.Metadata.Class
|
|
|
|
import Hasura.RQL.DDL.Schema
|
|
|
|
import Hasura.RQL.DML.Count
|
|
|
|
import Hasura.RQL.DML.Delete
|
|
|
|
import Hasura.RQL.DML.Insert
|
|
|
|
import Hasura.RQL.DML.Select
|
|
|
|
import Hasura.RQL.DML.Types
|
|
|
|
import Hasura.RQL.DML.Update
|
|
|
|
import Hasura.RQL.Types
|
|
|
|
import Hasura.RQL.Types.Run
|
[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
|
|
|
import Hasura.Server.Types
|
2021-04-16 16:26:11 +03:00
|
|
|
import Hasura.Server.Version (HasVersion)
|
2021-01-07 12:04:22 +03:00
|
|
|
import Hasura.Session
|
|
|
|
|
|
|
|
|
|
|
|
data RQLQuery
|
|
|
|
= RQInsert !InsertQuery
|
|
|
|
| RQSelect !SelectQuery
|
|
|
|
| RQUpdate !UpdateQuery
|
|
|
|
| RQDelete !DeleteQuery
|
|
|
|
| RQCount !CountQuery
|
|
|
|
| RQRunSql !RunSQL
|
2021-03-11 21:17:41 +03:00
|
|
|
| RQMssqlRunSql !MSSQL.MSSQLRunSQL
|
2021-05-21 05:46:58 +03:00
|
|
|
| RQCitusRunSql !RunSQL
|
2021-04-12 13:18:29 +03:00
|
|
|
| RQBigqueryRunSql !BigQuery.BigQueryRunSQL
|
|
|
|
| RQBigqueryDatabaseInspection !BigQuery.BigQueryRunSQL
|
2021-01-07 12:04:22 +03:00
|
|
|
| RQBulk ![RQLQuery]
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
$(deriveJSON
|
|
|
|
defaultOptions { constructorTagModifier = snakeCase . drop 2
|
|
|
|
, sumEncoding = TaggedObject "type" "args"
|
|
|
|
}
|
|
|
|
''RQLQuery)
|
|
|
|
|
|
|
|
runQuery
|
|
|
|
:: ( HasVersion
|
|
|
|
, MonadIO m
|
|
|
|
, MonadBaseControl IO m
|
|
|
|
, Tracing.MonadTrace m
|
|
|
|
, MonadMetadataStorage m
|
|
|
|
, MonadResolveSource m
|
|
|
|
)
|
|
|
|
=> Env.Environment
|
|
|
|
-> InstanceId
|
|
|
|
-> UserInfo
|
|
|
|
-> RebuildableSchemaCache
|
|
|
|
-> HTTP.Manager
|
2021-02-18 19:46:14 +03:00
|
|
|
-> ServerConfigCtx
|
2021-01-07 12:04:22 +03:00
|
|
|
-> RQLQuery
|
|
|
|
-> m (EncJSON, RebuildableSchemaCache)
|
2021-02-18 19:46:14 +03:00
|
|
|
runQuery env instanceId userInfo schemaCache httpManager serverConfigCtx rqlQuery = do
|
2021-02-19 05:39:30 +03:00
|
|
|
(metadata, currentResourceVersion) <- fetchMetadata
|
2021-01-07 12:04:22 +03:00
|
|
|
result <- runQueryM env rqlQuery & Tracing.interpTraceT \x -> do
|
|
|
|
(((js, tracemeta), meta), rsc, ci) <-
|
|
|
|
x & runMetadataT metadata
|
|
|
|
& runCacheRWT schemaCache
|
|
|
|
& peelRun runCtx
|
|
|
|
& runExceptT
|
|
|
|
& liftEitherM
|
|
|
|
pure ((js, rsc, ci, meta), tracemeta)
|
2021-02-19 05:39:30 +03:00
|
|
|
withReload currentResourceVersion result
|
2021-01-07 12:04:22 +03:00
|
|
|
where
|
2021-02-18 19:46:14 +03:00
|
|
|
runCtx = RunCtx userInfo httpManager serverConfigCtx
|
2021-01-07 12:04:22 +03:00
|
|
|
|
2021-02-19 05:39:30 +03:00
|
|
|
withReload currentResourceVersion (result, updatedCache, invalidations, updatedMetadata) = do
|
2021-01-07 12:04:22 +03:00
|
|
|
when (queryModifiesSchema rqlQuery) $ do
|
2021-05-21 05:46:58 +03:00
|
|
|
case _sccMaintenanceMode serverConfigCtx of
|
2021-04-06 06:25:02 +03:00
|
|
|
MaintenanceModeDisabled -> do
|
2021-02-18 19:46:14 +03:00
|
|
|
-- set modified metadata in storage
|
2021-04-06 06:25:02 +03:00
|
|
|
newResourceVersion <- setMetadata currentResourceVersion updatedMetadata
|
|
|
|
-- notify schema cache sync
|
|
|
|
notifySchemaCacheSync newResourceVersion instanceId invalidations
|
2021-02-18 19:46:14 +03:00
|
|
|
MaintenanceModeEnabled ->
|
|
|
|
throw500 "metadata cannot be modified in maintenance mode"
|
2021-01-07 12:04:22 +03:00
|
|
|
pure (result, updatedCache)
|
|
|
|
|
|
|
|
queryModifiesSchema :: RQLQuery -> Bool
|
|
|
|
queryModifiesSchema = \case
|
2021-01-29 08:48:17 +03:00
|
|
|
RQRunSql q -> isSchemaCacheBuildRequiredRunSQL q
|
|
|
|
RQBulk l -> any queryModifiesSchema l
|
|
|
|
_ -> False
|
2021-01-07 12:04:22 +03:00
|
|
|
|
|
|
|
runQueryM
|
|
|
|
:: ( HasVersion
|
|
|
|
, MonadError QErr m
|
|
|
|
, MonadIO m
|
|
|
|
, MonadBaseControl IO m
|
|
|
|
, UserInfoM m
|
|
|
|
, CacheRWM m
|
2021-01-29 08:48:17 +03:00
|
|
|
, HasServerConfigCtx m
|
2021-01-07 12:04:22 +03:00
|
|
|
, Tracing.MonadTrace m
|
|
|
|
, MetadataM m
|
|
|
|
)
|
|
|
|
=> Env.Environment -> RQLQuery -> m EncJSON
|
|
|
|
runQueryM env = \case
|
2021-04-16 16:26:11 +03:00
|
|
|
RQInsert q -> runInsert env q
|
|
|
|
RQSelect q -> runSelect q
|
|
|
|
RQUpdate q -> runUpdate env q
|
|
|
|
RQDelete q -> runDelete env q
|
|
|
|
RQCount q -> runCount q
|
2021-05-21 05:46:58 +03:00
|
|
|
RQRunSql q -> runRunSQL @'Vanilla q
|
2021-04-16 16:26:11 +03:00
|
|
|
RQMssqlRunSql q -> MSSQL.runSQL q
|
2021-05-21 05:46:58 +03:00
|
|
|
RQCitusRunSql q -> runRunSQL @'Citus q
|
2021-04-16 16:26:11 +03:00
|
|
|
RQBigqueryRunSql q -> BigQuery.runSQL q
|
2021-04-12 13:18:29 +03:00
|
|
|
RQBigqueryDatabaseInspection q -> BigQuery.runDatabaseInspection q
|
2021-04-16 16:26:11 +03:00
|
|
|
RQBulk l -> encJFromList <$> indexedMapM (runQueryM env) l
|