Reputation: 233
I want to build a multi-tenant solution with global and custom roles.
The application's authorization will be built based on permissions.
Then there may be defined a role that combines multiple permissions.
I want to provide several predefined global roles that can be used by everybody.
Additionally, I want to provide tenants with the ability to define their own custom roles.
At the moment, I have the following draft of the schema design (postgres):
create table tenants (
id uuid primary key,
...
);
create table permissions (
code character varying(30) primary key
);
create table roles (
id uuid primary key,
tenant_id uuid null references tenants,
...
);
create unique index on roles (id, tenant_id) where tenant_id is not null;
create table role_permissions (
role_id uuid not null references roles,
permission_id character varying(30) not null references permissions,
unique (role_id, permission_id)
);
create table users (
id uuid not null,
tenant_id uuid not null references tenants,
...
primary key (id, tenant_id)
);
create table user_roles (
tenant_id uuid not null,
user_id uuid not null,
role_tenant_id uuid null,
role_id uuid not null references roles,
foreign key (user_id, tenant_id) references users (id, tenant_id),
check (customer_id = role_tenant_id or role_tenant_id is null)
);
In this schema I'm not able to correctly reference roles from user_roles.
Is it possible to implement such constraint in postgres?
Upvotes: 1
Views: 235
Reputation: 12484
I assume that references to the customer
table and customer_id
starting with the definition of your users
table really mean to refer to tenant
and tenant_id
.
At some point you need to trust your code to be correct. If that is not good enough for you, and you must have constraints, then this is what I would do:
create or replace function user_role_check(_user_id uuid, _role_id uuid)
returns boolean as $$
select count(*) = 1
from roles r
join users u
on u.tenant_id = r.tenant_id
where u.id = _user_id
and r.id = _role_id;
$$ language sql;
create table user_roles (
id uuid not null primary key,
user_id uuid references users(id),
role_id uuid references roles(id),
check (user_role_check(user_id, role_id)),
unique (user_id, role_id)
);
Otherwise you are stuck duplicating tenant_id
into user_roles
.
Upvotes: 2