Symfony 4 y Sonata Admin: seguridad mediante voters

26 julio
por Rafa Cortés Berbegal

Si estamos desarrollando nuestra aplicación web en Symfony 4 y hemos optado por usar como «bundle» de administración Sonata, llegará un momento si la aplicación es medianamente compleja que tendremos que definir la gestión de privilegios a aplicar a los usuarios que pueden acceder al entorno de administración o «backend».

La documentación de Sonata nos da básicamente 2 alternativas para gestionar la seguridad:

  • Gestión mediante ROLES (sonata.admin.security.handler.role)

La gestión mediante ROLES consiste en definir roles a los que asociar a los usuarios y en función de dichos roles dar acceso a modificar, eliminar, etc las diferentes entidades de la aplicación. Esta opción es poco flexible ya que si le das permiso a un rol para modificar un tipo de objeto podrá modificar todos los objetos de ese tipo sin restricción.

La gestión mediante ACL es bastante compleja y en Symfony 4 se ha eliminado del desarrollo principal y se necesita instalar un «bundle» adicional para disponer de ella:  ACL Bundle.

Como alternativa a ACL en la documentación de Symfony se recomienda usar voters, combinándolos con los roles de usuario podemos abordar la gestión de privilegios en Sonata sin necesidad de implementar ACL.

Puedes ver y descargar todo el código que comentaré a continuación desde los enlaces de interés.

Combinando roles y voters en Symfony 4 y Sonata

Vamos a plantear un ejemplo en el que ver el uso de voters de forma sencilla. Estamos desarrollando una aplicación web para la gestión de concesionarios de una marca de automóviles, los usuarios con el rol de «concesionario» tendrán acceso de edición a los vehículos vendidos o por vender. Los propietarios de los vehículos podrán ver aquellos vehículos que posean y los administradores de la plataforma tendrán todos esos privilegios y también la opción de eliminar los vehículos. Existirán por tanto estos roles en la aplicación:

  • ROLE_ADMIN (Administradores)
  • ROLE_CONCESIONARIO (trabajadores del concesionario)
  • ROLE_PROPIETARIO (Propietario de vehículos)

Por comodidad en la administración, usaremos los grupos de usuarios que proporciona Sonata User Bundle y crearemos 3 grupos de usuarios. Con el propio panel de administración de Sonata aparte de asignar los usuarios a grupos también podremos darle roles específicos si fuese necesario, los 3 grupos de usuarios serían:

Grupos usarios symfony y sonata admin

 

La estructura de la base de datos quedaría de la siguiente forma:

Estructura base de datos

 

La configuración de roles en config/packages/security.yaml para implementar la política de seguridad deseada quedaría de la siguiente forma:

Con esta configuración de roles delimitamos los accesos por objetos, los administradores pueden listar, editar, crear, ver y eliminar vehículos, los concesionarios listar, editar y ver y los propietarios solo listar y ver. Sin el uso de voters estos privilegios son generales y da igual si los vehículos pertenecen o no a un concesionario que los usuarios con ese rol los podrán editar todos al igual que un propietario podrá ver cualquier vehículo sin necesidad de que le pertenezca. Mantengo el rol de superusuario (ROLE_SUPER_ADMIN) que tendrá acceso a todo por encima de nuestra política de seguridad.

Para añadir el uso de voters debemos realizar los siguientes cambios en los archivos de configuración:

  • config/packages/security.yaml

 

Añadimos la estrategia «unanimous» al determinar el acceso o no, ya que nosotros vamos a definir unas políticas mediante voters pero aparte tenemos los Roles, al obligar a que la decisión sea unánime, todas las políticas deben permitir el acceso para que éste se conceda. Se pueden ver las diferentes estrategias disponibles en la documentación de Symfony.

  • config/packages/sonata_admin.yaml

 

Definimos nuestro propio gestor de seguridad en la configuración de Sonata Admin, y vamos a implementarlo, creamos el archivo src/Security/Handler/VoterSecurityHandler.php con el siguiente contenido:

Esta clase con ligeras variaciones la obtuve de este gran artículo (Usando Voters con Sonata) de Sergio Gómez, que explica e implementa el sistema de voters con Sonata en versiones de Symfony anteriores a la 4. Te recomiendo que consultes en artículo de Sergio para obtener más información del funcionamiento de la clase.

 

Damos de alta el servicio en config/services.yaml:

 

Ahora vamos a crear nuestro voter para el objeto vehículo que determinará quien puede hacer qué con él según los criterios que hemos establecido. Creamos el archivo src/Voter/VehiculoVoter.php con el siguiente contenido:

Con el método «supports» nos aseguramos que el objeto se del tipo deseado (Vehículo) y que las acciones a comprobar sean las que tiene definidas dicho objeto. Luego con el método «votoOnAttribute» implementamos la lógica que queremos, en la línea 30 damos acceso completo a los usuarios que tienen los roles SUPER_ADMIN y/o ADMIN, de la 34 a la 39 comprobamos para aquellos usuarios con rol CONCESIONARIO que el vehículo en cuestión esté dado de alta en al menos uno de los concesionarios en los que están dados de alta los usuarios, y por último de la línea 41 a la 44 comprobamos que los usuarios con el rol PROPIETARIO además sean propietarios del vehículo.

Un tema importante a tener en cuenta es que es recomendable ir de roles con más privilegios a menos, así aquellos usuarios que tengan varios roles por ejemplo un usuario que esté a la vez en el grupo de administradores y concesionarios, con la primera comprobación ya se le concede el acceso si lo hacemos al revés habría que pasar por varias comprobaciones.

Ahora ya tenemos implementada la política de seguridad deseada, salvo un detalle, Sonata en los listados no aplica los voters y muestra todos los resultados, por ejemplo si accedemos con el usuario «propietario1» al listado de vehículos vemos los siguiente:

Listado vehículos Sonata

 

El usuario «propietario1» solo puede entrar en la ficha del vehículo del que es propietario tal y como hemos definido en el voter, pero sí puede ver el resto de vehículos aunque no sean suyos, para solventarlo debemos modificar la consulta encargada de generar el listado, modificamos la clase Admin de vehículo para Sonata:

 

Si volvemos a acceder al listado de vehículos con el usuario «propietario1», tenemos el listado que buscamos:

Listado vehiculos propietario filtrado Sonata

 

Este sería el listado que vería un trabajador del «Concesionario Alicante»:

Listado vehículos conesioario Sonata

 

Y este el listado de un usuario administrador:

Listado vehiculos administador Sonata

 

Ya tenemos implementada la política de seguridad que necesitamos para el objeto «Vehículo», con el mismo sistema añadiendo un voter para cada objeto de nuestra aplicación podemos definir prácticamente cualquier política por complicada que sea.

Si necesitas consultar algún archivo en concreto o todo el contenido en general, lo he dejado publicado en el siguiente repositorio de GitHub , ten en cuenta que el código es solo de ejemplo y solo tiene implementado lo que se ha visto en el presente artículo.

 

Enlaces de interés:

  • Usando Voters con Sonata > Artículo de Sergio Gómez, en el que me he basado principalmente para implementar los voters, realizando los cambios necesarios para Symfony 4.
  • Documentación voters  > Documentación oficial de Symfony 4.1 sobre el uso de voters.
  • Repositorio en GitHub > Con todo el contenido y funcionalidad vista en el artículo, eres libre de clonarlo y «jugar» con él.
Categorías:Desarrollo Web
Contáctanos
966 27 81 05 info@avanzaeninternet.com
O si lo prefieres
Visítanos
C/ Gabriel Miró 45, 3º I 03420 Castalla (Alicante)
de lunes a jueves de 9:00 a 14:00 h. de 15:00 a 18:00 h.
viernes de 9:00 a 14:00 h.
Nuestras Redes Sociales