Foreign data table based permission checks
The current permission checks are based on hasura-auth service calling our custom Camunda GraphQL with
query {
camunda_AuthenticatedUser {
id
vasaraAllowedIds
vasaraManagedIds
}
}
and then X-Hasura-Allowed-Ids
and X-Hasura-Managed-Ids
(again, whose values are decided by our custom GraphQL code) are used in Hasura permission rules to decide if user permission to see or manage the row in question.
The are the greatest pitfalls of this approach:
- Permission check will get slower while the amount of runtime and historic processes increase.
- Because Hasura may pass the lists back to Camunda, it will need to allow unnecessary big header size to be able to process requests with those lists in their header.
The alternative proposed here to try is to use Postgres Foreign Data feature to make Camunda tables visible in Hasura "entity database" and build permission to use those tables directly.
Preconditions
Postgres Foreign Data support must be configured with admin permissions into the "entity database":
-
postgres_fdw
must be enabled
CREATE EXTENSION IF NOT EXISTS postgres_fdw SCHEMA public;
- Foreign data wrapper server for accessing Camunda database must be configured
CREATE SERVER IF NOT EXISTS camunda FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'localhost', port '5432', dbname 'camunda');
- Credentials for the "entity database" user to access Camunda database must be recorded
CREATE USER MAPPING IF NOT EXISTS FOR hasura SERVER camunda OPTIONS (user 'camunda', password 'camunda');
- The "entity database" user must be given access to use the created "foreign data wrapper server
GRANT USAGE ON FOREIGN SERVER camunda TO hasura;
Foreign tables
With working foreign data wrapper, we are able to create a new schema for Camunda tables
CREATE SCHEMA camunda;
and populate it with the tables we need to access from Camunda database:
- Authorization records, of course
CREATE FOREIGN TABLE camunda.act_ru_authorization (
id_ character varying(64) NOT NULL,
rev_ integer NOT NULL,
type_ integer NOT NULL,
group_id_ character varying(255),
user_id_ character varying(255),
resource_type_ integer NOT NULL,
resource_id_ character varying(255),
perms_ integer,
removal_time_ timestamp without time zone,
root_proc_inst_id_ character varying(64)
)
SERVER camunda
OPTIONS (
schema_name 'public',
table_name 'act_ru_authorization'
);
- Historic process instance records, because we need to map resources ids for runtime and historic process instance authorizations to their business keys
CREATE FOREIGN TABLE camunda.act_hi_procinst (
id_ character varying(64) NOT NULL,
proc_inst_id_ character varying(64) NOT NULL,
business_key_ character varying(255),
proc_def_key_ character varying(255),
proc_def_id_ character varying(64) NOT NULL,
start_time_ timestamp without time zone NOT NULL,
end_time_ timestamp without time zone,
removal_time_ timestamp without time zone,
duration_ bigint,
start_user_id_ character varying(255),
start_act_id_ character varying(255),
end_act_id_ character varying(255),
super_process_instance_id_ character varying(64),
root_proc_inst_id_ character varying(64),
super_case_instance_id_ character varying(64),
case_inst_id_ character varying(64),
delete_reason_ character varying(4000),
tenant_id_ character varying(64),
state_ character varying(255)
)
SERVER camunda
OPTIONS (
schema_name 'public',
table_name 'act_hi_procinst'
);
- Historic task instance records, because we need to map resources ids for runtime and historic task instance authorizations to the business keys of their processes
CREATE FOREIGN TABLE camunda.act_hi_taskinst (
id_ character varying(64) NOT NULL,
task_def_key_ character varying(255),
proc_def_key_ character varying(255),
proc_def_id_ character varying(64),
root_proc_inst_id_ character varying(64),
proc_inst_id_ character varying(64),
execution_id_ character varying(64),
case_def_key_ character varying(255),
case_def_id_ character varying(64),
case_inst_id_ character varying(64),
case_execution_id_ character varying(64),
act_inst_id_ character varying(64),
name_ character varying(255),
parent_task_id_ character varying(64),
description_ character varying(4000),
owner_ character varying(255),
assignee_ character varying(255),
start_time_ timestamp without time zone NOT NULL,
end_time_ timestamp without time zone,
duration_ bigint,
delete_reason_ character varying(4000),
priority_ integer,
due_date_ timestamp without time zone,
follow_up_date_ timestamp without time zone,
tenant_id_ character varying(64),
removal_time_ timestamp without time zone
)
SERVER camunda
OPTIONS (
schema_name 'public',
table_name 'act_hi_taskinst'
);
The columns for the tables were fetched from Camunda 7.14 postgres schema.
These tables should make it possible to build understandable ACL views, which are easily usable for Hasura permissions:
- Process definition ACL, e.g. to determine if user should be able to edit forms for a process or create "entity" rows for a specific process
CREATE OR REPLACE VIEW camunda.acl_definition AS
SELECT act_ru_authorization.id_ AS id,
act_ru_authorization.user_id_,
act_ru_authorization.group_id_,
((act_ru_authorization.perms_ & 256) = 256) AS "create",
((act_ru_authorization.perms_ & 2) = 2) AS read,
((act_ru_authorization.perms_ & 4) = 4) AS update,
((act_ru_authorization.perms_ & 16) = 16) AS delete,
act_ru_authorization.resource_id_ AS proc_def_
FROM camunda.act_ru_authorization
WHERE ((act_ru_authorization.type_ = 1) AND (act_ru_authorization.resource_type_ = 6));
Check Camunda source for the permissions values.
- task instance ACL, e.g. to determine if user should be able to view a specific task form
CREATE OR REPLACE VIEW camunda.acl_task AS
SELECT act_ru_authorization.id_ AS id,
act_ru_authorization.user_id_,
act_ru_authorization.group_id_,
((act_ru_authorization.perms_ & 8) = 8) AS "create",
((act_ru_authorization.perms_ & 2) = 2) AS read,
((act_ru_authorization.perms_ & 4) = 4) AS update,
((act_ru_authorization.perms_ & 16) = 16) AS delete,
act_hi_taskinst.task_def_key_,
act_hi_taskinst.proc_def_key_,
act_hi_procinst.business_key_
FROM ((camunda.act_ru_authorization
JOIN camunda.act_hi_taskinst ON (((act_ru_authorization.resource_id_)::text = (act_hi_taskinst.id_)::text)))
JOIN camunda.act_hi_procinst ON (((act_hi_taskinst.proc_inst_id_)::text = (act_hi_procinst.id_)::text)))
WHERE ((act_ru_authorization.type_ = 1) AND ((act_ru_authorization.resource_type_ = 7) OR (act_ru_authorization.resource_type_ = 19)));
Joining authorizations with historic task instance and historic process instance tables gives us business key related to a specific task instance authorization.
- Instance ACL combining authorizations process instances and tasks, e.g. to determine which permission user should have a specific "entity table" row by business key
CREATE OR REPLACE VIEW camunda.acl_instance AS
SELECT act_ru_authorization.id_ AS id,
act_ru_authorization.user_id_,
act_ru_authorization.group_id_,
((act_ru_authorization.perms_ & 8) = 8) AS "create",
((act_ru_authorization.perms_ & 2) = 2) AS read,
((act_ru_authorization.perms_ & 4) = 4) AS update,
((act_ru_authorization.perms_ & 16) = 16) AS delete,
act_hi_taskinst.proc_def_key_,
act_hi_procinst.business_key_
FROM ((camunda.act_ru_authorization
JOIN camunda.act_hi_taskinst ON (((act_ru_authorization.resource_id_)::text = (act_hi_taskinst.id_)::text)))
JOIN camunda.act_hi_procinst ON (((act_hi_taskinst.proc_inst_id_)::text = (act_hi_procinst.id_)::text)))
WHERE ((act_ru_authorization.type_ = 1) AND ((act_ru_authorization.resource_type_ = 7) OR (act_ru_authorization.resource_type_ = 19)))
UNION
SELECT act_ru_authorization.id_ AS id,
act_ru_authorization.user_id_,
act_ru_authorization.group_id_,
((act_ru_authorization.perms_ & 8) = 8) AS "create",
((act_ru_authorization.perms_ & 2) = 2) AS read,
((act_ru_authorization.perms_ & 4) = 4) AS update,
((act_ru_authorization.perms_ & 16) = 16) AS delete,
act_hi_procinst.proc_def_key_,
act_hi_procinst.business_key_
FROM (camunda.act_ru_authorization
JOIN camunda.act_hi_procinst ON (((act_ru_authorization.resource_id_)::text = (act_hi_procinst.id_)::text)))
WHERE ((act_ru_authorization.type_ = 1) AND ((act_ru_authorization.resource_type_ = 8) OR (act_ru_authorization.resource_type_ = 20)));
Example: select user tasks forms
These ACL views for Camunda tables can only be used in authorization rules once they have a relationship with the table in question. For example, the following relationship would connect task ACL view with user task form table by user task id and process definition key:
- table:
schema: vasara
name: user_task_form
array_relationships:
- name: authorizations
using:
manual_configuration:
remote_table:
schema: camunda
name: acl_task
insertion_order: null
column_mapping:
user_task_id: task_def_key_
process_definition_key: proc_def_key_
Now, finally, it is possible to define a row permission rule that only allows to select rows, which have a related READ authorization with either user or group of the current user
select_permissions:
- role: user
permission:
columns:
- id
- metadata
- process_definition_key
- process_definition_version
- user_task_id
- schema
- process_definition_version_tag
- settings
filter:
authorizations:
_and:
- read:
_eq: true
- _or:
- user_id_:
_eq: X-Hasura-User-Id
- group_id_:
_in: X-Hasura-Groups
allow_aggregations: true