There are two options for you here: the without referential integrity and the with. A without for sure is simpler, if you do not care about the lack of foreign keys, but if you find it indispensable there is an alternative with them - however you will not escape creating an additional table for each entity of your model.
For starters, create the basic templates: Usuario
, Grupo
, Permissao
, and the many-to-many associations GrupoUsuario
, PermissaoUsuario
and PermissaoGrupo
. This separation will make your life much easier. Making it clear: a user belongs to zero or more groups and has zero or more permissions. In addition, the user inherits group permissions (i.e. do not need to explicitly assign a permission if one of the groups where it is already has this permission).
You can omit Grupo
if you want to simplify your model, at the expense of a larger number of objects of the PermissaoUsuario
(as each new user will have to explicitly receive all permissions). Alternatively, you can omit PermissaoUsuario
if you are not interested in giving individual permissions to a user, only indirectly through a group. It is up to you.
Solution without referential integrity
The model Permissao
should be kept as simple as possible: nome
of permission, the tabela
which she refers to and linha
in the table (possibly NULL
), if applicable. Example:
Permissao
nome tabela linha
-------------------------------------
create Produtos NULL <- Usuário pode criar Produtos
read Produtos 42 <- Usuário pode ler o produto 42
read Fornecedores NULL <- Usuário pode ler Fornecedores
Checking if a user is allowed to access a record is simple:
select 1
from Usuario u
join PermissaoUsuario pu on pu.id_usuario = u.id
join Permissao p on pu.id_permissao = p.id
where
u.id = 10 and
p.nome = 'read' and
p.tabela = 'Produtos' and
(p.linha = 42 or p.linha is null)
union
select 1
from Usuario u
join GrupoUsuario gu on gu.id_usuario = u.id
join PermissaoGrupo pg on pg.id_grupo = gu.id_grupo
join Permissao p on pu.id_permissao = p.id
where
u.id = 10 and
p.nome = 'read' and
p.tabela = 'Produtos' and
(p.linha = 42 or p.linha is null);
(Remembering: if you omitted Grupo
or PermissaoUsuario
the bottom or top of the union
, respectively, it is not necessary; and this test is null
is only in case a user has permission to access all table rows, if your template does not have this then you can omit that part)
Solution with referential integrity
In this case your model Permissao
will contain only the nome
(i.e. will not have tabela
nor linha
), but a number of other templates will be created to represent the permission to access a specific line of a specific model. For example:
create table PermissaoProduto(
id_permissao integer not null,
id_produto integer not null,
primary key (id_permissao),
foreign key (id_permissao) references Permissao(id),
foreign key (id_produto) references Produtos(id)
);
The rest stays the same. So, every line of PermissaoProduto
will be inheriting of a line of Permissao
- because both have the same primary key. The referential integrity to Usuario
is on account of Permissao
, the integrity referenced to Produtos
is on account of PermissaoProduto
. Permission check is equally simple, it only has one join
the most:
select 1
from Usuario u
join PermissaoUsuario pu on pu.id_usuario = u.id
join Permissao p on pu.id_permissao = p.id
join PermissaoProduto pp on p.id = pp.id_permissao
where
u.id = 10 and
p.nome = 'read' and
pp.id_produto = 42
union
select 1
from Usuario u
join GrupoUsuario gu on gu.id_usuario = u.id
join PermissaoGrupo pg on pg.id_grupo = gu.id_grupo
join Permissao p on pu.id_permissao = p.id
join PermissaoProduto pp on p.id = pp.id_permissao
where
u.id = 10 and
p.nome = 'read' and
pp.id_produto = 42;
Although it seems complicated at first glance to keep a different permissions table for each model, the integration between each type of permission and the user is the same - because it happens through the table Permissao
. Personally, I would use the solution without reference integrity, but at the discretion of each.
It is not a desgin Pattern, but what is missing there is a table
Perfil
. A user has one or more profiles, and access permissions are linked to the profile. Among these permissions, some relate toCategorias
(who knows a tablePermissaoCategoria
containing the permit id and the category id).– Anthony Accioly
Note that in this new modeling there is no problem a permission not to be related to any category (and, break it is possible to assign a permission to several categories and vice versa).
– Anthony Accioly
Are your permissions "per table" (e.g., "user X can access products") or "per line" (e.g., "user X can access product Y")? A common pattern is the RBAC, I can write a simplified answer describing it. But first I need this question clarified.
– mgibsonbr
Are by line, User X can access product Y and also need permissions for "CRUD".
– Tuyoshi Vinicius