Skip to content

Commit 8549035

Browse files
plcplcgithub-actions[bot]
authored andcommitted
RFC: (Table) Permissions in MySQL
This PR introduces an RFC for permissions in MySQL. (solves hasura#2097) [Rendered](https://github.com/hasura/graphql-engine-mono/blob/plc/rfc/mysql-permissions/rfcs/permissions-mysql.md) hasura/graphql-engine-mono#2183 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> GitOrigin-RevId: e63fb9d
1 parent dc4713b commit 8549035

File tree

2 files changed

+222
-0
lines changed

2 files changed

+222
-0
lines changed

rfcs/permissions-mysql.md

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Table Permissions in MySQL
2+
3+
## Metadata
4+
```
5+
---
6+
authors: Philip Lykke Carlsen <[email protected]>
7+
discussion:
8+
https://github.com/hasura/graphql-engine-mono/pull/2183
9+
state: published
10+
---
11+
```
12+
13+
## Description
14+
15+
We want to support the [role-based access control
16+
feature](https://hasura.io/docs/latest/graphql/core/auth/authorization/index.html)
17+
on MySQL in the same fasihion that it works on Postgres currently.
18+
19+
The role-based access control feature, often referred to simply as "Permissions"
20+
allows Hasura users to restrict what data is returned by queries and admitted by
21+
mutations. Several flavors of permissions exist:
22+
23+
**_Column Permissions_** censor the columns that cliens in a given role have access
24+
to (either in Queries or Mutations), by means of an explicit list of columns
25+
exposed.
26+
27+
**_Row Permissions_** censor table rows returned or affected, on the basis of a
28+
boolean-returning SQL expression, which is allowed to reference the columns of
29+
the table as well as session variables. These are sometimes also known as
30+
_Filter Permissions_ in the server code. Both _Queries_ and _Mutations_ may be
31+
subjected to _Row Permissions_.
32+
33+
The query semantics of _Column Permissions_ and _Row Permissions_ is that,
34+
in the context of a role:
35+
36+
> _For all intents and purposes, the dataset that a query ranges over includes
37+
only those columns and rows that the **Column Permissions** and **Row
38+
Permissions** of that role allow._
39+
40+
For example:
41+
* A user is unable to delete rows that her role's delete-permissions do not
42+
include. It is not an error to try; from the point of view of the delete
43+
mutation the rows indicated by the query are just not there.
44+
* A query for an aggregation (say, the average `age` in a `persons` table) will
45+
only consider those rows that her role's select-permissions include.
46+
47+
The **_Aggregation Permission_** (a single boolean) decides if the query root
48+
fields that relate to aggregations should appear in the schema.
49+
50+
**_Limit Permissions_** (an integer) apply only to queries and limit the
51+
maximum number of rows any query may yield. Importantly, an active _Limit
52+
Permission_ does not influence the row domain of an aggregation query, only the
53+
maximum number of rows produced by the query, see <a name="footnote-1-ref"></a>[[1]](#footnote-1-def).
54+
55+
Last, **_Inherited Roles_** may compose permissions, see [User
56+
docs](https://hasura.io/docs/latest/graphql/core/auth/authorization/inherited-roles.html#select-permissions).
57+
An _Inherited Role_ is an authorization role defined in terms of other
58+
pre-existing roles. The permissions that an _Inherited Role_ grants are _not_
59+
just the point-wise union of each of the parent roles' permission syntax, but
60+
rather the _union of the query datasets_ that each parent role permits, in the
61+
sense described above.
62+
63+
This introduces a complication: In isolation, a role's _Column Permissions_ and
64+
_Row Permissions_ describe a "rectangular" dataset, with columns along one side
65+
and rows along the other. For two or more roles however, when we union the
66+
datasets they permit we do not necessarily end up with a rectangular dataset:
67+
68+
![Inherited roles diagram](permissions-mysql/Inherited%20roles%20permissions.png)
69+
70+
Our data universe however only permits "rectangular" data. In order to
71+
accomodate the complexity resulting from _Inherited Roles_ we make columns that
72+
are particular to a single parent role nullable. For example, in the diagram
73+
above we would return `null` for (`Row 5`, `Column B`) and (`Row 2`, `Column
74+
D`).
75+
76+
## What does this concretely look like
77+
78+
When this is implemented, it should be possible to set permissions on MySQL
79+
tables in exactly the same fashion as is possible on Postgres tables, and
80+
queries and mutations should respect those permissions.
81+
82+
At the time of this writing, this means every tracked MySQL tables has a
83+
_Permissions_ tab in the Console, which allows a user to set permissions on:
84+
85+
* Rows and Columns
86+
* For each of the CRUD actions
87+
* Using all the predicates supported as boolean operators in `_where: {..}`
88+
arguments in queries to MySQL tables.
89+
* Limit and Aggregation
90+
91+
The tests in
92+
[`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)
93+
pertaining to permissions should be generalised to multiple backends and made to
94+
pass for MySQL.
95+
96+
## How are we going to implement it
97+
98+
The GraphQL-Engine applies permissions at three points of processing:
99+
100+
1. When building the schema, where _Column Permissions_ may cause fields to be
101+
censored from the schema.
102+
2. When parsing an incoming GraphQL query into HGE IR, where _Column Permissions_
103+
again influence the grammar parsed, and _Row Permissions_ influence the IR
104+
generated such that relevant permissions are included.
105+
3. When SQL is generated from the IR, where the translation needs to take the IR
106+
node fields containing permissions into account.
107+
108+
Since parser/schema generation is a single unified abstraction in
109+
GraphQL-Engine, all a backend needs to do to support permissions is a suitable
110+
implementation of type class methods `MonadSchema.buildTableQueryFields`,
111+
`buildTableInsertMutationFields` etc..
112+
113+
`buildTableQueryFields` et al. are given as inputs a representation of the
114+
permissions for a table (in the context of some role), which for _Column
115+
Permissions_ list the exposed columns and for _Row Permissions_ contain
116+
backend-specific Boolean Expression IR fragments, which are supposed to end up
117+
in parser outputs.
118+
119+
There are already backend-generic implementations of these methods in
120+
`Hasura.GraphQL.Schema.Build` which we may use unless a product requirement
121+
surfaces that require us to deviate from the (de facto) standard table schema.
122+
123+
The inputting and storing of permissions in metadata is handled completely
124+
generically by the core infrastructure referencing only the backend-defined
125+
notions of column names and boolean expressions and how to (de-)serialize them.
126+
The only work that is required here is to expose API endpoints for the various
127+
CRUD-actions on permissions.
128+
129+
### Development plan for Queries
130+
131+
1. Implement the `instance MonadSchema 'MySQL` using the backend-generic default
132+
implementations.
133+
134+
2. Enabling the API for manipulating permissions amounts to is adding
135+
`tablePermissionsCommands @MySQL` to the `metadataV1CommandParsers`
136+
implementation of the `BackendAPI MySQL` instance.
137+
138+
3. For SQL generation of a _Query_, the case that translates an `AnnSelectG`
139+
<a name="footnote-2-ref"></a>[[2]](#footnote-2-def). Any applicable _Row
140+
Permissions_ and _Limit Permissions_ are found in
141+
the field `_asnPerm` and need to translate into `WHERE` and `LIMIT` clauses
142+
respectably.
143+
144+
4. Also for SQL generation of a _Query_, the case that translates an
145+
`AnnColumnField` <a name="footnote-3-ref"></a>[[3]](#footnote-3-def)
146+
needs to observe the field `_acfCaseBoolExpression`, which decides
147+
whether the column value should be nullified, as resulting from
148+
inherited roles.
149+
150+
### Notes for Mutations
151+
152+
A GQL _Mutation_ however may result in either of `INSERT`, `UPDATE`, or `DELETE`
153+
statements. Of these, `INSERT` has no obvious point in which to include a permissions
154+
predicate over the rows inserted.
155+
156+
As a consequence of this we need to translate an insert-mutation into a MySQL
157+
transaction, where performing the mutation and checking permissions on the
158+
affected rows is split over multiple statements. <a name="footnote-4-ref"></a>[[4]](#footnote-4-def)
159+
160+
One suggested way to do this could be making a temporary table having the
161+
permissions as `CHECK`-constraints, inserting the new rows into this table
162+
(which fails if the permissions are not satisfied) and copying them over to the
163+
table actually targeted by the mutation.
164+
165+
## Future
166+
167+
This document is a product of its time, brought into existence by the
168+
contemporary need to elaborate on how permissons work because the development
169+
work on MySQL needs to incorporate them.
170+
171+
An insight resulting from discussing this subject is that it would be more
172+
appropriate to treat permissions not as a distinct topic of a dedicated RFC
173+
document, but rather as associated concepts of the RFCs of the objects they
174+
apply to, i.e. variants of _Queries_ and _Mutations_.
175+
176+
As it were, _permissions_ do not exist in a vacuum. In order to talk about them
177+
we need to also talk about what they apply to. As such it makes for a more
178+
elegant exposition to talk about permissons as associated aspects of the subject
179+
they act on.
180+
181+
It it therefore expected that this document be superseded by dedicated RFCs on
182+
the subjects of _Queries_, _Mutations_.
183+
184+
## Questions
185+
186+
How does the feature of _Inherited Roles_ interact with the permissions-support
187+
in a backend?
188+
189+
> The permissions that result from Inherited Roles are completely resolved into
190+
> base permissions before being handed over to schema building. So Inherited
191+
> Roles have no interaction with backend code.
192+
193+
Do _Limit Permissions_ only apply to root-fields or also to array relationships?
194+
195+
> Yet unanswered.
196+
197+
## Footnotes
198+
199+
<a name="footnote-1-def"></a>[1][^](#footnote-1-ref): For example,
200+
201+
```
202+
query {
203+
articles_aggregate {
204+
count
205+
nodes { .. }
206+
}
207+
}
208+
```
209+
210+
If the select permission on some role `r` specifies a limit of `5` and there are
211+
a total of `10` rows accessible to `r` (as per active _Row Permissions_), the
212+
`count` in the above query should return `10` while `nodes` should only return
213+
`5`. I.e, the _Limit Permission_ should only be applied when returning rows and
214+
not when computing aggregate data.
215+
216+
<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).
217+
218+
<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.
219+
220+
<a name="footnote-4-def"></a>[4][^](#footnote-4-ref): In PostgreSQL we exploit that `INSERT` supports a
221+
`RETURNING` clause that lets us extract information from the affected rows.
222+
MySQL does not support this.
7.6 KB
Loading

0 commit comments

Comments
 (0)