Como posso fazer o controle de acesso através de uma tabela SQL?
-
03-07-2019 - |
Pergunta
Eu estou tentando criar um sistema de controle de acesso.
Aqui está um exemplo simplificada do que a mesa eu estou tentando controlar o acesso a aparência:
things table:
id group_id name
1 1 thing 1
2 1 thing 2
3 1 thing 3
4 1 thing 4
5 2 thing 5
E a tabela de controle de acesso esta aparência:
access table:
user_id type object_id access
1 group 1 50
1 thing 1 10
1 thing 2 100
O acesso pode ser concedida, indicando o identificador do 'coisa' diretamente, ou concedida para um grupo inteiro de coisas, especificando um ID de grupo. No exemplo acima, o usuário 1 foi concedido um nível de acesso de 50 ao grupo 1, que devem ser aplicadas a menos que existam quaisquer outras regras que garantam acesso mais específico para uma coisa individual.
Eu preciso de uma consulta que retorna uma lista de coisas (IDS é apenas ok), juntamente com o nível de acesso de um usuário específico. Então, usando o exemplo acima eu iria querer algo assim para identificação de usuário 1:
desired result:
thing_id access
1 10
2 100
3 50 (things 3 and 4 have no specific access rule,
4 50 so this '50' is from the group rule)
5 (thing 5 has no rules at all, so although I
still want it in the output, there's no access
level for it)
O mais próximo que eu posso vir acima com é o seguinte:
SELECT *
FROM things
LEFT JOIN access ON
user_id = 1
AND (
(access.type = 'group' AND access.object_id = things.group_id)
OR (access.type = 'thing' AND access.object_id = things.id)
)
Mas que retorna várias linhas, quando eu só quero um para cada linha na tabela 'coisas'. Eu não tenho certeza de como chegar até uma única linha para cada 'coisa', ou como priorizar regras 'coisa' sobre as regras 'grupo'.
Se ajudar, o banco de dados que estou usando é PostgreSQL.
Por favor, sinta-se livre para deixar um comentário se há alguma informação que eu perdi.
Agradecemos antecipadamente!
Solução
Eu não sei o dialeto Postgres SQL, mas talvez algo como:
select thing.*, coalesce ( ( select access
from access
where userid = 1
and type = 'thing'
and object_id = thing.id
),
( select access
from access
where userid = 1
and type = 'group'
and object_id = thing.group_id
)
)
from things
A propósito, eu não gosto do design. Eu preferiria tabela de acesso a ser dividida em duas:
thing_access (user_id, thing_id, access)
group_access (user_id, group_id, access)
Meu consulta torna-se então:
select thing.*, coalesce ( ( select access
from thing_access
where userid = 1
and thing_id = thing.id
),
( select access
from group_access
where userid = 1
and group_id = thing.group_id
)
)
from things
Eu prefiro isso porque chaves estrangeiras agora pode ser usado nas tabelas de acesso.
Outras dicas
Acabei de ler um artigo na noite passada sobre este assunto. Ele tem algumas idéias sobre como fazer isso. Se você não pode usar o link no título tente usar o Google Scholar na Divulgação limitante na Hipócrates bancos de dados.
Embora existam várias respostas boas, o mais eficiente seria provavelmente algo como isto:
SELECT things.id, things.group_id, things.name, max(access)
FROM things
LEFT JOIN access ON
user_id = 1
AND (
(access.type = 'group' AND access.object_id = things.group_id)
OR (access.type = 'thing' AND access.object_id = things.id)
)
group by things.id, things.group_id, things.name
Que simplesmente usa sumarização adicionado a você consulta para obter o que você está procurando.
Tony:
Nem uma solução ruim, eu gosto, parece funcionar. Aqui está a sua consulta após pequenos ajustes:
SELECT
things.*,
coalesce (
( SELECT access
FROM access
WHERE user_id = 1
AND type = 'thing'
AND object_id = things.id
),
( SELECT access
FROM access
WHERE user_id = 1
AND type = 'group'
AND object_id = things.group_id
)
) AS access
FROM things;
E os resultados parecem corretas:
id | group_id | name | access
----+----------+---------+--------
1 | 1 | thing 1 | 10
2 | 1 | thing 2 | 100
3 | 1 | thing 3 | 50
4 | 1 | thing 4 | 50
5 | 2 | thing 5 |
eu completamente tomar o ponto sobre ele não ser um esquema ideal. No entanto, estou preso com ele até certo ponto.
Josef:
A sua solução é muito semelhante ao material que eu estava brincando com, e meus instintos (tais como eles são) me dizer que ele deve ser possível fazê-lo dessa forma. Infelizmente, não produz resultados completamente corretas:
id | group_id | name | max
----+----------+---------+-----
1 | 1 | thing 1 | 50
2 | 1 | thing 2 | 100
3 | 1 | thing 3 | 50
4 | 1 | thing 4 | 50
5 | 2 | thing 5 |
O nível de acesso para 'coisa 1' tomou o valor de acesso 'grupo' maior, ao invés do valor específico de acesso 'coisa' mais de 10, que é o que eu estou atrás. Eu não acho que há uma maneira de corrigir isso dentro de um GROUP BY
, mas se alguém tiver alguma sugestão que eu sou mais do que feliz de ser provado errado sobre esse ponto.