Vamos supor que eu tenha uma tabela de itens:
CREATE TABLE items
(
item serial PRIMARY KEY,
...
);
Agora, quero introduzir o conceito de "permissões" para cada item (observe, aqui não estou falando de permissões de acesso ao banco de dados, mas de permissões de lógica de negócios para esse item). Cada item tem permissões padrão e também permissões por usuário que podem substituir as permissões padrão.
Tentei pensar em várias maneiras de implementar isso e vi as seguintes soluções, mas não tenho certeza sobre qual é o melhor e por que:
1) A solução booleana
Use uma coluna booleana para cada permissão:
CREATE TABLE items
(
item serial PRIMARY KEY,
can_change_description boolean NOT NULL,
can_change_price boolean NOT NULL,
can_delete_item_from_store boolean NOT NULL,
...
);
CREATE TABLE item_per_user_permissions
(
item int NOT NULL REFERENCES items(item),
user int NOT NULL REFERENCES users(user),
PRIMARY KEY(item, user),
can_change_description boolean NOT NULL,
can_change_price boolean NOT NULL,
can_delete_item_from_store boolean NOT NULL,
...
);
Vantagens : Cada permissão é nomeada.
Desvantagens : Existem dezenas de permissões que aumentam significativamente o número de colunas e você deve defini-las duas vezes (uma vez em cada tabela).
2) A Solução Inteira
Use um número inteiro e trate-o como um campo de bits (ou seja, o bit 0 é para can_change_description
, o bit 1 é para can_change_price
e assim por diante, e use operações bit a bit para definir ou ler permissões).
CREATE DOMAIN permissions AS integer;
Vantagens : muito rápido.
Desvantagens : Você deve acompanhar qual bit representa a permissão no banco de dados e na interface front-end.
3) A solução Bitfield
O mesmo que 2), mas use bit(n)
. Provavelmente as mesmas vantagens e desvantagens, talvez um pouco mais lentas.
4) A solução Enum
Use um tipo de enum para as permissões:
CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);
e crie uma tabela extra para permissões padrão:
CREATE TABLE item_default_permissions
(
item int NOT NULL REFERENCES items(item),
perm permission NOT NULL,
PRIMARY KEY(item, perm)
);
e altere a tabela de definição por usuário para:
CREATE TABLE item_per_user_permissions
(
item int NOT NULL REFERENCES items(item),
user int NOT NULL REFERENCES users(user),
perm permission NOT NULL,
PRIMARY KEY(item, user, perm)
);
Vantagens : Fácil de nomear permissões individuais (você não precisa lidar com posições de bits).
Desvantagens : Mesmo ao recuperar as permissões padrão, é necessário acessar duas tabelas adicionais: primeiro, a tabela de permissões padrão e, segundo, o catálogo do sistema que armazena os valores de enumeração.
Especialmente porque as permissões padrão devem ser recuperadas para cada visualização de página única desse item , o impacto no desempenho da última alternativa pode ser significativo.
5) A solução de matriz Enum
Igual a 4), mas use uma matriz para armazenar todas as permissões (padrão):
CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);
CREATE TABLE items
(
item serial PRIMARY KEY,
granted_permissions permission ARRAY,
...
);
Vantagens : Fácil de nomear permissões individuais (você não precisa lidar com posições de bits).
Desvantagens : quebra a 1ª forma normal e é um pouco feio. Ocupa um número considerável de bytes em uma linha se o número de permissões for grande (cerca de 50).
Você consegue pensar em outras alternativas?
Qual abordagem deve ser adotada e por quê?
Observe: esta é uma versão modificada de uma pergunta postada anteriormente no Stackoverflow .
fonte
bigint
campos (cada um bom para 64 bits) ou uma sequência de bits. Escrevi algumas respostas relacionadas ao SO que podem ser úteis.Respostas:
Sei que você não está perguntando sobre a segurança do banco de dados em si , mas pode fazer o que quiser usando a segurança do banco de dados. Você pode até usar isso em um aplicativo da web. Se você não deseja usar a segurança do banco de dados, os esquemas ainda se aplicam.
Você deseja segurança no nível da coluna, segurança no nível da linha e, provavelmente, gerenciamento de funções hierárquico. A segurança baseada em funções é muito mais fácil de gerenciar do que a segurança baseada no usuário.
Este código de exemplo é para o PostgreSQL 9.4, que será lançado em breve. Você pode fazer isso com a 9.3, mas há mais trabalho manual necessário.
Você quer que tudo seja indexável se estiver preocupado com o desempenho †, o que deveria ser. Isso significa que os campos de máscara de bits e matriz provavelmente não serão uma boa ideia.
Neste exemplo, mantemos as principais tabelas de dados no
data
esquema e as visualizações correspondentes empublic
.Coloque um gatilho em data.thing para inserções e atualizações, impondo que a coluna do proprietário seja o current_user. Talvez permita apenas que o proprietário exclua seus próprios registros (outro gatilho).
Crie uma
WITH CHECK OPTION
visualização, que é o que os usuários realmente usarão. Tente realmente atualizá-lo, caso contrário você precisará de gatilhos / regras, o que é mais trabalhoso.Em seguida, crie uma tabela da lista de controle de acesso:
Altere sua visualização para considerar ACLs:
Crie uma tabela de privilégios de linha padrão:
Coloque um gatilho na inserção em data.thing para que ele copie os privilégios de linha padrão para security.thing_acl.
grantor
eadmin_option
colunas às tabelas acl para rastrear quem concedeu o privilégio e se o donatário pode gerenciar privilégios nessa linha.† Nesse caso, pg_has_role provavelmente não é indexável. Você precisaria obter uma lista de todas as funções superiores ao current_user e comparar com o valor do proprietário / donatário.
fonte
Você já pensou em usar a extensão PostgreSQL da Lista de controle de acesso ?
Ele contém o tipo de dados ACE nativo do PostgreSQL e um conjunto de funções que permitem verificar se um usuário tem permissão para acessar dados. Ele funciona com o sistema de funções do PostgreSQL ou com números abstratos (ou UUIDs) que representam os IDs de usuário / função do aplicativo.
No seu caso, basta adicionar uma coluna ACL às suas tabelas de dados e usar uma das
acl_check_access
funções para verificar um usuário em relação a uma ACL.O uso de ACLs é uma maneira extremamente flexível de lidar com permissões de lógica de negócios. Além disso, é incrivelmente rápido - a sobrecarga média é de apenas 25% do tempo necessário para ler um registro. A única limitação é que ele suporta no máximo 16 permissões personalizadas por tipo de objeto.
fonte
Eu posso pensar em outra possibilidade de codificar isso, a relacional
Se você não precisar da
permission_per_item
mesa, pode ignorá-la, conectar-sePermissions
eItems
diretamente àitem_per_user_permissions
mesa.diagrama de legenda
fonte