Esquema de base de datos para ACL


Quiero crear un esquema para una ACL; sin embargo, estoy dividido entre un par de formas de implementarlo.

Estoy bastante seguro de que no quiero lidiar con los permisos en cascada, ya que eso conduce a mucha confusión en el backend y para los administradores del sitio.

Creo que también puedo vivir con usuarios que solo están en un rol a la vez. Una configuración como esta permitirá agregar roles y permisos según sea necesario a medida que el sitio crezca sin afectar los roles/reglas existentes.

Al principio I iba a normalizar los datos y tener tres tablas para representar las relaciones.

ROLES { id, name }
RESOURCES { id, name }
PERMISSIONS { id, role_id, resource_id }

Una consulta para averiguar si a un usuario se le permitió algún lugar se vería así:

SELECT id FROM resources WHERE name = ?
SELECT * FROM permissions WHERE role_id = ? AND resource_id = ? ($user_role_id, $resource->id)

Entonces me di cuenta de que solo tendré unos 20 recursos, cada uno con hasta 5 acciones (crear, actualizar, ver, etc.)..) y quizás otros 8 roles. Esto significa que puedo ejercer un desprecio flagrante por la normalización de datos, ya que nunca tendré más de un par de cientos de registros posibles.

So tal vez un esquema como este tendría más sentido.

ROLES { id, name }
PERMISSIONS { id, role_id, resource_name }

Que me permitiría buscar registros en una sola consulta

SELECT * FROM permissions WHERE role_id = ? AND permission  = ? ($user_role_id, 'post.update')

Entonces, ¿cuál de estos es más correcto? ¿Hay otros esquemas para ACL?

 26
Author: mawburn, 2011-05-04

3 answers

En mi experiencia, la pregunta real se descompone principalmente en si va a ocurrir o no cualquier cantidad de restricción de acceso específica del usuario.

Supongamos, por ejemplo, que está diseñando el esquema de una comunidad y que permite a los usuarios alternar la visibilidad de su perfil.

Una opción es atenerse a un indicador de perfil público/privado y atenerse a comprobaciones de permisos amplias y preventivas: 'usuarios.ver ' (vistas de usuarios públicos) vs, digamos, 'usuarios.view_all ' (ve todos los usuarios, para moderador).

Otro implica permisos más refinados, es posible que desee que sean capaces de configurar las cosas para que puedan hacerse (a) visibles para todos, (b) visibles para sus amigos seleccionados a mano, (c) mantenerse en privado por completo, y tal vez (d) visibles para todos, excepto sus bozos seleccionados a mano. En este caso, debe almacenar datos relacionados con el propietario / acceso para filas individuales, y deberá abstraer en gran medida algunas de estas cosas para evitar materializar el cierre transitivo de una gráfico denso y orientado.

Con cualquiera de los dos enfoques, he encontrado que la complejidad añadida en la edición/asignación de roles se compensa con la facilidad/flexibilidad resultante en asignando permisos a piezas individuales de datos, y que lo siguiente funcionó mejor:

  1. Los usuarios pueden tener múltiples roles
  2. Roles y permisos combinados en la misma tabla con un indicador para distinguir los dos (útil al editar roles/permanentes)
  3. Los roles pueden asignar otros roles, y roles y perms puede asignar permisos (pero los permisos no pueden asignar roles), desde dentro de la misma tabla.

El gráfico orientado resultante se puede extraer en dos consultas, construirse de una vez por todas en un tiempo razonable utilizando el idioma que esté utilizando, y almacenarse en caché en Memcache o similar para su uso posterior.

A partir de ahí, extraer los permisos de un usuario es una cuestión de verificar qué roles tiene, y procesarlos utilizando el gráfico de permisos para obtener el permiso. Compruebe los permisos verificando que un usuario tiene el rol/permiso especificado o no. Y luego ejecute su consulta / emita un error basado en esa verificación de permisos.

Puede extender la comprobación para nodos individuales (es decir, check_perms($user, 'users.edit', $node) para "puede editar este nodo" vs check_perms($user, 'users.edit') para "puede editar un nodo") si lo necesita, y tendrá algo muy flexible/fácil de usar para los usuarios finales.

Como el ejemplo de apertura debe ilustrar, tenga cuidado de dirigir demasiado hacia los permisos de nivel de fila. El el cuello de botella de rendimiento es menos en la comprobación de los permisos de un nodo individual que en la extracción de una lista de nodos válidos (es decir, solo aquellos que el usuario puede ver o editar). Desaconsejaría cualquier cosa más allá de los campos flags y user_id dentro de las propias filas si no está (muy) bien versado en la optimización de consultas.

 28
Author: Denis de Bernardy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-10 13:19:19

Esto significa que puedo ejercer descaradamente desprecio por la normalización de datos como I nunca tendrá más de un par cientos de posibles registros.

El número de filas que espera no es un criterio para elegir a qué forma normal apuntar. La normalización se refiere a la integridad de los datos. Por lo general, aumenta la integridad de los datos al reducir la redundancia.

La verdadera pregunta no es "¿Cuántas filas tendré?", pero " ¿Qué tan importante es para la base de datos ¿siempre me das las respuestas correctas?"Para una base de datos que se utilizará para implementar una ACL, yo diría" Bastante peligroso importante."

En todo caso, un número bajo de filas sugiere que no necesita preocuparse por el rendimiento, por lo que 5NF debería ser una elección fácil de hacer. Usted querrá golpear 5NF antes de agregar cualquier número de identificación.

Una consulta para averiguar si un usuario fue permitido en algún lugar se vería como esto:

SELECT id FROM resources WHERE name = ?
SELECT * FROM permissions 
WHERE role_id = ? AND resource_id = ? ($user_role_id, $resource->id)

Que escribiste como dos consultas en lugar de usar una unión interior sugiere que podrías estar por encima de tu cabeza. (Eso es una observación, no una crítica.)

SELECT p.* 
FROM permissions p
INNER JOIN resources r ON (r.id = p.resource_id AND 
                           r.name = ?)
 7
Author: Mike Sherrill 'Cat Recall',
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-08 23:24:00

Puede usar un CONJUNTO para asignar los roles.

CREATE TABLE permission (
  id integer primary key autoincrement
  ,name varchar
  ,perm SET('create', 'edit', 'delete', 'view')
  ,resource_id integer );
 0
Author: Johan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-03 21:21:58