graphql-engine
graphql-engine copied to clipboard
Use x-hasura-* values in JSONB permission fields
Hello!
When setting permission rules, we are using the _contains rule on a JSONB field. If all values are hardcoded, this works perfectly.
Using x-hasura-* session variables does not seem to be possible.
Example:

GraphQL Query:
query MyQuery {
appointment {
id
}
}
Generated Query (from "Analyze"):
SELECT
coalesce(json_agg("root"), '[]') AS "root"
FROM
(
SELECT
row_to_json(
(
SELECT
"_1_e"
FROM
(
SELECT
"_0_root.base"."id" AS "id"
) AS "_1_e"
)
) AS "root"
FROM
(
SELECT
*
FROM
"public"."appointment"
WHERE
(
("public"."appointment"."resource") @ > (('{"user":"X-Hasura-User-Id"}') :: jsonb)
)
) AS "_0_root.base"
) AS "_2_root"
The query seems to be built correctly, but the x-hasura-user-id value is not replaced.
The RHS of any operator, in this case _contains, can either by an X-hasura-* variable or a valid literal.
We have noted this feature request though. Thanks !
The RHS of any operator, in this case
_contains, can either by anX-hasura-*variable or a valid literal.We have noted this feature request though. Thanks !
Hi @tirumaraiselvan , I'm afraid I didn't quite understand your point, could you please clarify? What's RHS in this case?
@m4rcelofs RHS is the value that you provide for the operator, in your case: {"user": "x-hasura-user-id"}. This is neither a x-hasura-* variable nor a pure json literal. EDIT: To be more precise, hasura interprets this as a pure json literal because it cannot resolve session variables which are inside json expressions.
Would be cool, if hasura could implement SQL expressions that could return any value and compare it against the required value:
Something like this:
_sql: "SELECT resource -> 'user'",
_val: x-hasura-user-id
OR in my current case:
_or:
- _sql: "SELECT settings -> 'membership_approval'",
_val: "PUBLIC",
- members:
_and:
- type:
_eq: "ADMIN"
- user_id:
_eq: x-hasura-user-id
An additional thing that would be nice here would be to make sure it also works with postgres arrays.
For instance, let's say you have a view with an array_agg user_ids field that contains all the user ids that can see a given row returned by the view. If you manually construct the where user_ids @> '{<some_user_id>}' in Postgres this works just fine, but if I try the below:
filter:
user_ids:
_contains:
- X-Hasura_User_Id
The metadata refuses to import with
"filter": {
"user_ids": {
"_contains": [
"X-Hasura_User_Id"
]
}
},
...
"reason": "in table \"api.my_view\": in permission for role \"user\": is of type \"_uuid\"; this operator works only on columns of type \"jsonb\""
I expect that this should probably work, given that the underlying manual query does.
@tirumaraiselvan's explanation is correct, but let me expand on this a bit. TL;DR: it's possible to do, but isn't an easy task, and will require some scoping / prioritization on our end.
We currently do not try to interpret the value for the operator, and we just make the following distinction:
- either we recognize a string that matches the pattern for an environment variable
- or we forward the value verbatim, treating as an uninterpreted expression
In the codebase, this is represented by the PartialSQLExp type, which cover those two cases.
The reason why we originally didn't want to interpret the given value is that we don't necessarily know how to interpret values for that particular column type, and we don't want to risk rejecting a value that the database would actually accept (not all JSON values are valid GraphQL values, for instance). As a result, in all places we deal with values, such as in the IR, and in the translation layers, we have that same assumption: a value is either an unprocessed blob, or a session variable, nothing more complex.
So, if we wanted to do this, we would have to parse all incoming values into something that we can inspect and traverse, but that does the strict minimum amount of translation, to guarantee that we can always reconstruct something that has the correct shape, which means, in short, reimplementing an ad-hoc representation of JSON values; something like:
data UserProvidedValue leaf
= LeafValue leaf
| ObjectValue (HashMap Text (UserProvidedValue leaf))
| ArrayValue (Array (UserProvidedValue leaf))
deriving (Functor, Traversable)
-- each leaf value is either a session variable or some unprocessed JSON blob
type PermissionValue = UserProvidedValue PartialSQLExp
The same change would then have to be propagated in every place that handles leaf values throughout the IR, such as making UnpreparedValue recursive, and changing all translation layers to deal with those.
Doing this would also raise several other questions:
- what do we do if for some reason we fail to parse the value as such a pseudo-JSON value? we still want to represent and forward it unprocessed to the backend, as it might still be valid?
- how can a user indicate which strings within the object need to be interpreted as session variables, and which need to be kept verbatim? is it okay to treat every string we encounter in our traversal as a potential session variable?
So, yeah, to summarize: doable, but not trivial. ^^' We could start by writing an RFC detailing how we want to approach this?
anything new on this
Up this. I have similar case. What can i do in my case? I want to filter permission by their departments
the above config doesn't work. What can i do to make it work, please?
+1 for this feature
We were able to get around this specific issue by using association tables, but that may not work for everyone's use-case.
+1 for this feature.