hasura / hasura/graphql-engine

Question: adding roles permissions for functions

Open
#3,845 2 comments 0 reactions 0 assignees View on GitHub
k/question
Dominant language
TypeScript
Stars
32.1k
Forks
3k
PR merge metrics
PR metrics pending

Description

Hello,

I'm having a bit of difficulty, it's a bit difficult to put in words but I've created a simple application that demos the issue. Here is the code:

```
CREATE EXTENSION IF NOT EXISTS "citext";
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE products (
id uuid DEFAULT uuid_generate_v1() PRIMARY KEY,
name citext NOT NULL,
active boolean DEFAULT true NOT NULL
);

CREATE TABLE tags (
id uuid DEFAULT uuid_generate_v1() PRIMARY KEY,
name text NOT NULL
);

CREATE TABLE product_tags (
id uuid DEFAULT uuid_generate_v1() PRIMARY KEY,
product_id uuid REFERENCES products ON DELETE CASCADE,
tag_id uuid REFERENCES tags ON DELETE CASCADE
);

CREATE TABLE product_search_results
(
product_id uuid NOT NULL REFERENCES products (id)
);

CREATE TABLE product_search_tags_result
(
tag_id uuid NOT NULL REFERENCES tags (id),
count bigint NOT NULL
);

CREATE OR REPLACE FUNCTION product_search(tag_ids uuid[])
RETURNS SETOF product_search_results AS
$$
BEGIN
RETURN QUERY SELECT product_id
FROM product_tags
WHERE tag_id = ANY (tag_ids)
GROUP BY product_id;
END
$$ LANGUAGE plpgsql STABLE;

CREATE OR REPLACE FUNCTION product_search_tags(tag_ids uuid[], only_active bool)
RETURNS SETOF product_search_tags_result AS
$$
BEGIN
RETURN QUERY SELECT tags.id, COUNT(*)
FROM product_search(tag_ids) product_search_result
LEFT JOIN products ON products.id = product_search_result.product_id
LEFT JOIN product_tags
ON product_tags.product_id = product_search_result.product_id
LEFT JOIN tags on tags.id = product_tags.tag_id
WHERE
CASE WHEN only_active THEN
products.active = true
ELSE
true
END
GROUP BY tags.id;
END
$$ LANGUAGE plpgsql STABLE;

```

As you can see, there is a table, `products`, a table, `tags`, and a table `product_tags`, which joins products with tags. `products` have a column, `active`, which is a boolean value.

There's a function, `product_search`, which returns a list of `product_search_results` (here it's just a list of product ids, in the real app it's more complex). You give it a list of tag ids, and it will return all the product ids with a relationship (in `product_tags`) with those tag ids. There's a second function, `product_search_tags`, which also takes a list of tag ids, does a `product_search`, then gives you the tags associated associated with the products found. So, for example, if you have two tags, `tag_one` and `tag_two`, and a product, `product_one`, which is associated with both tags, if you make a select with `product_search_tags` passing a list containing only the id of `tag_one`, it will perform the `product_search`, find `product_one`, then return both of the tag ids because they are both associated with `product_one`. It also returns the count of products associated with both the given tag ids and the returned ids. Apologies if my explanation isn't super clear, but it should be simple enough to figure out if you play around with it.

As you can see, `product_search_tags` contains a second parameter, `only_active`. If `only_active` is set to true, `product_search_tags` will only return the tags associated with active products, and the count of active products associated with those tags.

I'd like to be able to be able to limit anonymous users' access to inactive products. At the moment with Hasura, it's not possible to set permissions on functions. Do you have any suggestions how I could do this?

I've posted my metadata.json below. Thank you very much.

```
{
"functions": [
{
"function": "product_search",
"configuration": {}
},
{
"function": "product_search_tags",
"configuration": {}
}
],
"remote_schemas": [],
"query_collections": [],
"allowlist": [],
"version": 2,
"tables": [
{
"table": "product_search_results",
"is_enum": false,
"configuration": {
"custom_root_fields": {
"select": null,
"select_by_pk": null,
"select_aggregate": null,
"insert": null,
"update": null,
"delete": null
},
"custom_column_names": {}
},
"object_relationships": [
{
"using": {
"foreign_key_constraint_on": "product_id"
},
"name": "product",
"comment": null
}
],
"array_relationships": [],
"insert_permissions": [],
"select_permissions": [
{
"role": "anonymous",
"comment": null,
"permission": {
"allow_aggregations": false,
"computed_fields": [],
"columns": [],
"filter": {
"product": {
"active": {
"_eq": true
}
}
}
}
}
],
"update_permissions": [],
"delete_permissions": [],
"event_triggers": [],
"computed_fields": []
},
{
"table": "product_search_tags_result",
"is_enum": false,
"configuration": {
"custom_root_fields": {
"select": null,
"select_by_pk": null,
"select_aggregate": null,
"insert": null,
"update": null,
"delete": null
},
"custom_column_names": {}
},
"object_relationships": [
{
"using": {
"foreign_key_constraint_on": "tag_id"
},
"name": "tag",
"comment": null
}
],
"array_relationships": [],
"insert_permissions": [],
"select_permissions": [
{
"role": "anonymous",
"comment": null,
"permission": {
"allow_aggregations": false,
"computed_fields": [],
"columns": [],
"filter": {}
}
}
],
"update_permissions": [],
"delete_permissions": [],
"event_triggers": [],
"computed_fields": []
},
{
"table": "product_tags",
"is_enum": false,
"configuration": {
"custom_root_fields": {
"select": null,
"select_by_pk": null,
"select_aggregate": null,
"insert": null,
"update": null,
"delete": null
},
"custom_column_names": {}
},
"object_relationships": [
{
"using": {
"foreign_key_constraint_on": "product_id"
},
"name": "product",
"comment": null
},
{
"using": {
"foreign_key_constraint_on": "tag_id"
},
"name": "tag",
"comment": null
}
],
"array_relationships": [],
"insert_permissions": [],
"select_permissions": [
{
"role": "anonymous",
"comment": null,
"permission": {
"allow_aggregations": false,
"computed_fields": [],
"columns": [],
"filter": {
"product": {
"active": {
"_eq": true
}
}
}
}
}
],
"update_permissions": [],
"delete_permissions": [],
"event_triggers": [],
"computed_fields": []
},
{
"table": "products",
"is_enum": false,
"configuration": {
"custom_root_fields": {
"select": null,
"select_by_pk": null,
"select_aggregate": null,
"insert": null,
"update": null,
"delete": null
},
"custom_column_names": {}
},
"object_relationships": [],
"array_relationships": [],
"insert_permissions": [],
"select_permissions": [
{
"role": "anonymous",
"comment": null,
"permission": {
"allow_aggregations": false,
"computed_fields": [],
"columns": [],
"filter": {
"active": {
"_eq": true
}
}
}
}
],
"update_permissions": [],
"delete_permissions": [],
"event_triggers": [],
"computed_fields": []
},
{
"table": "tags",
"is_enum": false,
"configuration": {
"custom_root_fields": {
"select": null,
"select_by_pk": null,
"select_aggregate": null,
"insert": null,
"update": null,
"delete": null
},
"custom_column_names": {}
},
"object_relationships": [],
"array_relationships": [],
"insert_permissions": [],
"select_permissions": [],
"update_permissions": [],
"delete_permissions": [],
"event_triggers": [],
"computed_fields": []
}
]
}
```

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the SQL functions from the issue and reviewing the provided metadata.json, focusing on the anonymous role permissions for the result tables and functions. Define how function-level permissions should be configured and how inactive products must be excluded; done means the requested access control is supported without exposing inactive products.

Written by the indexing model from the issue text.

Assessment

Tech stack
postgres
Domain
api, authorization, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.