¿Cómo puedo hacer el control de acceso a través de una tabla SQL?
-
03-07-2019 - |
Pregunta
Estoy tratando de crear un sistema de control de acceso.
Este es un ejemplo simplificado de cómo se ve la tabla a la que estoy tratando de controlar el acceso:
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
Y la tabla de control de acceso se ve así:
access table:
user_id type object_id access
1 group 1 50
1 thing 1 10
1 thing 2 100
El acceso se puede otorgar ya sea especificando el ID de la 'cosa' directamente, o otorgado para un grupo completo de cosas especificando un ID de grupo. En el ejemplo anterior, al usuario 1 se le ha otorgado un nivel de acceso de 50 al grupo 1, que debe aplicarse a menos que haya otras reglas que otorguen un acceso más específico a una cosa individual.
Necesito una consulta que devuelva una lista de cosas (solo identificaciones, está bien) junto con el nivel de acceso para un usuario específico. Entonces, usando el ejemplo anterior, me gustaría algo como esto para el ID de usuario 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)
Lo más cercano que puedo encontrar es esto:
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)
)
Pero eso devuelve varias filas, cuando solo quiero una para cada fila en la tabla "cosas". No estoy seguro de cómo llegar a una sola fila para cada "cosa", o cómo priorizar las reglas de "cosa" sobre las reglas de "grupo".
Si ayuda, la base de datos que estoy usando es PostgreSQL.
Por favor, siéntase libre de dejar un comentario si hay alguna información que haya omitido.
Gracias de antemano!
Solución
No sé el dialecto de Postgres SQL, pero tal vez 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
Por cierto, no me gusta el diseño. Preferiría que la tabla de acceso se divida en dos:
thing_access (user_id, thing_id, access)
group_access (user_id, group_id, access)
Mi consulta se convierte en:
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
Prefiero esto porque ahora se pueden usar claves foráneas en las tablas de acceso.
Otros consejos
Acabo de leer un artículo anoche sobre esto. Tiene algunas ideas sobre cómo hacer esto. Si no puede usar el enlace en el título, intente usar Google Scholar en Limitación de la divulgación en Hipocrático Bases de datos.
Si bien hay varias respuestas buenas, la más eficiente probablemente sería algo como esto:
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 simplemente usa el resumen agregado a su consulta para obtener lo que está buscando.
Tony:
No es una mala solución, me gusta, parece funcionar. Aquí está su consulta después de pequeños 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;
Y los resultados se ven correctos:
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 |
Hago completamente entendiendo que no es un esquema ideal. Sin embargo, estoy atascado con él hasta cierto punto.
Josef:
Tu solución es muy similar a las cosas con las que jugaba, y mis instintos (como son) me dicen que debería ser posible hacerlo de esa manera. Desafortunadamente no produce resultados completamente correctos:
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 |
El nivel de acceso para 'cosa 1' ha tomado el valor de acceso de 'grupo' más alto, en lugar del valor de acceso de 'cosa' más específico de 10, que es lo que busco. No creo que haya una manera de solucionarlo dentro de un GROUP BY
, pero si alguien tiene alguna sugerencia, estoy más que contento de que se me demuestre lo contrario.