mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
RFC: (Table) Permissions in MySQL
This PR introduces an RFC for permissions in MySQL. (solves #2097) [Rendered](https://github.com/hasura/graphql-engine-mono/blob/plc/rfc/mysql-permissions/rfcs/permissions-mysql.md) https://github.com/hasura/graphql-engine-mono/pull/2183 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> GitOrigin-RevId: e63fb9ddfd3a8752e28536818193b04403635f0d
This commit is contained in:
parent
dc4713b9d3
commit
8549035d9b
222
rfcs/permissions-mysql.md
Normal file
222
rfcs/permissions-mysql.md
Normal file
@ -0,0 +1,222 @@
|
||||
# Table Permissions in MySQL
|
||||
|
||||
## Metadata
|
||||
```
|
||||
---
|
||||
authors: Philip Lykke Carlsen <philip@hasura.io>
|
||||
discussion:
|
||||
https://github.com/hasura/graphql-engine-mono/pull/2183
|
||||
state: published
|
||||
---
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
We want to support the [role-based access control
|
||||
feature](https://hasura.io/docs/latest/graphql/core/auth/authorization/index.html)
|
||||
on MySQL in the same fasihion that it works on Postgres currently.
|
||||
|
||||
The role-based access control feature, often referred to simply as "Permissions"
|
||||
allows Hasura users to restrict what data is returned by queries and admitted by
|
||||
mutations. Several flavors of permissions exist:
|
||||
|
||||
**_Column Permissions_** censor the columns that cliens in a given role have access
|
||||
to (either in Queries or Mutations), by means of an explicit list of columns
|
||||
exposed.
|
||||
|
||||
**_Row Permissions_** censor table rows returned or affected, on the basis of a
|
||||
boolean-returning SQL expression, which is allowed to reference the columns of
|
||||
the table as well as session variables. These are sometimes also known as
|
||||
_Filter Permissions_ in the server code. Both _Queries_ and _Mutations_ may be
|
||||
subjected to _Row Permissions_.
|
||||
|
||||
The query semantics of _Column Permissions_ and _Row Permissions_ is that,
|
||||
in the context of a role:
|
||||
|
||||
> _For all intents and purposes, the dataset that a query ranges over includes
|
||||
only those columns and rows that the **Column Permissions** and **Row
|
||||
Permissions** of that role allow._
|
||||
|
||||
For example:
|
||||
* A user is unable to delete rows that her role's delete-permissions do not
|
||||
include. It is not an error to try; from the point of view of the delete
|
||||
mutation the rows indicated by the query are just not there.
|
||||
* A query for an aggregation (say, the average `age` in a `persons` table) will
|
||||
only consider those rows that her role's select-permissions include.
|
||||
|
||||
The **_Aggregation Permission_** (a single boolean) decides if the query root
|
||||
fields that relate to aggregations should appear in the schema.
|
||||
|
||||
**_Limit Permissions_** (an integer) apply only to queries and limit the
|
||||
maximum number of rows any query may yield. Importantly, an active _Limit
|
||||
Permission_ does not influence the row domain of an aggregation query, only the
|
||||
maximum number of rows produced by the query, see <a name="footnote-1-ref"></a>[[1]](#footnote-1-def).
|
||||
|
||||
Last, **_Inherited Roles_** may compose permissions, see [User
|
||||
docs](https://hasura.io/docs/latest/graphql/core/auth/authorization/inherited-roles.html#select-permissions).
|
||||
An _Inherited Role_ is an authorization role defined in terms of other
|
||||
pre-existing roles. The permissions that an _Inherited Role_ grants are _not_
|
||||
just the point-wise union of each of the parent roles' permission syntax, but
|
||||
rather the _union of the query datasets_ that each parent role permits, in the
|
||||
sense described above.
|
||||
|
||||
This introduces a complication: In isolation, a role's _Column Permissions_ and
|
||||
_Row Permissions_ describe a "rectangular" dataset, with columns along one side
|
||||
and rows along the other. For two or more roles however, when we union the
|
||||
datasets they permit we do not necessarily end up with a rectangular dataset:
|
||||
|
||||
![Inherited roles diagram](permissions-mysql/Inherited%20roles%20permissions.png)
|
||||
|
||||
Our data universe however only permits "rectangular" data. In order to
|
||||
accomodate the complexity resulting from _Inherited Roles_ we make columns that
|
||||
are particular to a single parent role nullable. For example, in the diagram
|
||||
above we would return `null` for (`Row 5`, `Column B`) and (`Row 2`, `Column
|
||||
D`).
|
||||
|
||||
## What does this concretely look like
|
||||
|
||||
When this is implemented, it should be possible to set permissions on MySQL
|
||||
tables in exactly the same fashion as is possible on Postgres tables, and
|
||||
queries and mutations should respect those permissions.
|
||||
|
||||
At the time of this writing, this means every tracked MySQL tables has a
|
||||
_Permissions_ tab in the Console, which allows a user to set permissions on:
|
||||
|
||||
* Rows and Columns
|
||||
* For each of the CRUD actions
|
||||
* Using all the predicates supported as boolean operators in `_where: {..}`
|
||||
arguments in queries to MySQL tables.
|
||||
* Limit and Aggregation
|
||||
|
||||
The tests in
|
||||
[`server/tests-py/test_graphql_queries.py#L575`](https://github.com/hasura/graphql-engine-mono/blob/dfba245a4dbe1a71b1e3cc7c92914fc0a919c2b0/server/tests-py/test_graphql_queries.py#L575)
|
||||
pertaining to permissions should be generalised to multiple backends and made to
|
||||
pass for MySQL.
|
||||
|
||||
## How are we going to implement it
|
||||
|
||||
The GraphQL-Engine applies permissions at three points of processing:
|
||||
|
||||
1. When building the schema, where _Column Permissions_ may cause fields to be
|
||||
censored from the schema.
|
||||
2. When parsing an incoming GraphQL query into HGE IR, where _Column Permissions_
|
||||
again influence the grammar parsed, and _Row Permissions_ influence the IR
|
||||
generated such that relevant permissions are included.
|
||||
3. When SQL is generated from the IR, where the translation needs to take the IR
|
||||
node fields containing permissions into account.
|
||||
|
||||
Since parser/schema generation is a single unified abstraction in
|
||||
GraphQL-Engine, all a backend needs to do to support permissions is a suitable
|
||||
implementation of type class methods `MonadSchema.buildTableQueryFields`,
|
||||
`buildTableInsertMutationFields` etc..
|
||||
|
||||
`buildTableQueryFields` et al. are given as inputs a representation of the
|
||||
permissions for a table (in the context of some role), which for _Column
|
||||
Permissions_ list the exposed columns and for _Row Permissions_ contain
|
||||
backend-specific Boolean Expression IR fragments, which are supposed to end up
|
||||
in parser outputs.
|
||||
|
||||
There are already backend-generic implementations of these methods in
|
||||
`Hasura.GraphQL.Schema.Build` which we may use unless a product requirement
|
||||
surfaces that require us to deviate from the (de facto) standard table schema.
|
||||
|
||||
The inputting and storing of permissions in metadata is handled completely
|
||||
generically by the core infrastructure referencing only the backend-defined
|
||||
notions of column names and boolean expressions and how to (de-)serialize them.
|
||||
The only work that is required here is to expose API endpoints for the various
|
||||
CRUD-actions on permissions.
|
||||
|
||||
### Development plan for Queries
|
||||
|
||||
1. Implement the `instance MonadSchema 'MySQL` using the backend-generic default
|
||||
implementations.
|
||||
|
||||
2. Enabling the API for manipulating permissions amounts to is adding
|
||||
`tablePermissionsCommands @MySQL` to the `metadataV1CommandParsers`
|
||||
implementation of the `BackendAPI MySQL` instance.
|
||||
|
||||
3. For SQL generation of a _Query_, the case that translates an `AnnSelectG`
|
||||
<a name="footnote-2-ref"></a>[[2]](#footnote-2-def). Any applicable _Row
|
||||
Permissions_ and _Limit Permissions_ are found in
|
||||
the field `_asnPerm` and need to translate into `WHERE` and `LIMIT` clauses
|
||||
respectably.
|
||||
|
||||
4. Also for SQL generation of a _Query_, the case that translates an
|
||||
`AnnColumnField` <a name="footnote-3-ref"></a>[[3]](#footnote-3-def)
|
||||
needs to observe the field `_acfCaseBoolExpression`, which decides
|
||||
whether the column value should be nullified, as resulting from
|
||||
inherited roles.
|
||||
|
||||
### Notes for Mutations
|
||||
|
||||
A GQL _Mutation_ however may result in either of `INSERT`, `UPDATE`, or `DELETE`
|
||||
statements. Of these, `INSERT` has no obvious point in which to include a permissions
|
||||
predicate over the rows inserted.
|
||||
|
||||
As a consequence of this we need to translate an insert-mutation into a MySQL
|
||||
transaction, where performing the mutation and checking permissions on the
|
||||
affected rows is split over multiple statements. <a name="footnote-4-ref"></a>[[4]](#footnote-4-def)
|
||||
|
||||
One suggested way to do this could be making a temporary table having the
|
||||
permissions as `CHECK`-constraints, inserting the new rows into this table
|
||||
(which fails if the permissions are not satisfied) and copying them over to the
|
||||
table actually targeted by the mutation.
|
||||
|
||||
## Future
|
||||
|
||||
This document is a product of its time, brought into existence by the
|
||||
contemporary need to elaborate on how permissons work because the development
|
||||
work on MySQL needs to incorporate them.
|
||||
|
||||
An insight resulting from discussing this subject is that it would be more
|
||||
appropriate to treat permissions not as a distinct topic of a dedicated RFC
|
||||
document, but rather as associated concepts of the RFCs of the objects they
|
||||
apply to, i.e. variants of _Queries_ and _Mutations_.
|
||||
|
||||
As it were, _permissions_ do not exist in a vacuum. In order to talk about them
|
||||
we need to also talk about what they apply to. As such it makes for a more
|
||||
elegant exposition to talk about permissons as associated aspects of the subject
|
||||
they act on.
|
||||
|
||||
It it therefore expected that this document be superseded by dedicated RFCs on
|
||||
the subjects of _Queries_, _Mutations_.
|
||||
|
||||
## Questions
|
||||
|
||||
How does the feature of _Inherited Roles_ interact with the permissions-support
|
||||
in a backend?
|
||||
|
||||
> The permissions that result from Inherited Roles are completely resolved into
|
||||
> base permissions before being handed over to schema building. So Inherited
|
||||
> Roles have no interaction with backend code.
|
||||
|
||||
Do _Limit Permissions_ only apply to root-fields or also to array relationships?
|
||||
|
||||
> Yet unanswered.
|
||||
|
||||
## Footnotes
|
||||
|
||||
<a name="footnote-1-def"></a>[1][^](#footnote-1-ref): For example,
|
||||
|
||||
```
|
||||
query {
|
||||
articles_aggregate {
|
||||
count
|
||||
nodes { .. }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the select permission on some role `r` specifies a limit of `5` and there are
|
||||
a total of `10` rows accessible to `r` (as per active _Row Permissions_), the
|
||||
`count` in the above query should return `10` while `nodes` should only return
|
||||
`5`. I.e, the _Limit Permission_ should only be applied when returning rows and
|
||||
not when computing aggregate data.
|
||||
|
||||
<a name="footnote-2-def"></a>[2][^](#footnote-2-ref): See [`server/src-lib/Hasura/RQL/IR/Select.hs`]( https://github.com/hasura/graphql-engine-mono/blob/dfba245a4dbe1a71b1e3cc7c92914fc0a919c2b0/server/src-lib/Hasura/RQL/IR/Select.hs#L67).
|
||||
|
||||
<a name="footnote-3-def"></a>[3][^](#footnote-3-ref): See [`server/src-lib/Hasura/RQL/IR/Select.hs`]( https://github.com/hasura/graphql-engine-mono/blob/dfba245a4dbe1a71b1e3cc7c92914fc0a919c2b0/server/src-lib/Hasura/RQL/IR/Select.hs#L305). Haddocks contain descriptions of use.
|
||||
|
||||
<a name="footnote-4-def"></a>[4][^](#footnote-4-ref): In PostgreSQL we exploit that `INSERT` supports a
|
||||
`RETURNING` clause that lets us extract information from the affected rows.
|
||||
MySQL does not support this.
|
BIN
rfcs/permissions-mysql/Inherited roles permissions.png
Normal file
BIN
rfcs/permissions-mysql/Inherited roles permissions.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
Loading…
Reference in New Issue
Block a user