Zend_Acl: autorización y permisos en Zend Framework
In: zend framework
7 oct 2009Una de las varias APIs que ofrece Zend Framework es Zend_Acl, que ofrece el servicio para controlar las autorizaciones y permisos respecto a los roles y los recursos. Después de horas haciendo pruebas y moldeandola, ahora tengo un sistema de autorización eficaz, facilmente personalizable y muy práctico.
Primero hay que enumerar las distintas partes del sistema de permisos en que lo he divido para poderlo entender:
- Rol: perfil de un usuario al que se le asignaran o denegaran privilegios. Puede tener uno o varios roles padres, de los cuales heredará privilegios. En caso de conflicto, prevalece los privilegios aplicados al Rol, y si el conflicto es entre padres, tiene más importancia el privilegio del último padre.
- Recurso: son las zonas en las que se puede dividir la aplicación. Podría ser: editorial, marketing, administracion, …
- Subrecursos: son los componentes de los Recursos. Como en forma de árbol, son los hijos de los Recursos, y solamente tiene a un Recurso como padre.
- Permisos: son las acciones concretas que se pueden hacer sobre un Subrecurso.
Basándonos en el esquema que teníamos anteriormente (véase la entrada sobre Zend_Auth), haremos una ampliación del sistema para que además de comprobar la autentificación del usuario, también compruebe que el usuario tiene los privilegios para acceder a la petición realizada.
Para empezar, debemos configurar Zend_Acl: indicarles que roles existen, su parentesco si existe, los recursos, los subrecursos y los permisos permitidos o denegados. Para hacer esta operación más senzilla de cara a futuras ampliaciones o modificaciones del sistema de autorización, he creado una clase que extiende la clase Zend_Acl, y le añade funciones para cargar todos los datos desde un archivo ‘INI’. Tal clase se ha añadido dentro de una nueva librería llamada ‘My‘. De esta manera, la nueva clase se encuentra en la ruta ‘/library/My/Permission/Acl.php‘ (hay que modificar el Bootstrap.php para que cargue de la librería My).
El código de la clase solo tiene 5 funciones:
- __construct($file) : la función constructora recibe la ruta del archivo .INI donde estan los datos. Irá llamando a las otras funciones para ir cargando los datos y almacenandolos.
- setRoles($roles): añade los roles a la clase Zend_Acl.
- setResources($resources): añade los recursos a la clase Zend_Acl.
- setSubresources($subresources): añade los subrecursos a la clase Zend_Acl, declarando que Recurso es su padre.
- setPrivileges($subresources): añade los privlegios a la clase Zend_Acl, que pueden ser generales, a Recursos, a Subrecursos o a permisos.
De esta manera, cuando instancemos la clase pasandole el archivo INI, la clase se encargará de coger todos los valores y añadirselos.
/** * @package My * @subpackage Permission * @version v0.1 */ class My_Permission_Acl extends Zend_Acl { public function __construct($file) { // Carga los roles del archivo INI $roles = new Zend_Config_Ini($file, 'roles') ; // Crea los Roles dentro del Zend_Acl $this->_setRoles($roles); // Carga los recursos del archivo INI $resources = new Zend_Config_Ini($file, 'resources') ; // Crea los Recursos dentro del Zend_Acl $this->_setResources($resources) ; // Carga los subrecursos del archivo INI $subresources = new Zend_Config_Ini($file, 'subresources') ; // Crea los Subecursos dentro del Zend_Acl $this->_setSubresources($subresources) ; // Por cada Rol, se cargan sus Permisos foreach ($roles->toArray() as $role => $parents) { $privileges = new Zend_Config_Ini($file, $role) ; $this->_setPrivileges($role, $privileges) ; } } /** * _setRoles * * Añade los Roles al Zend_Acl * * @param Zend_Config * @return Zend_Acl */ protected function _setRoles($roles) { foreach ($roles as $role => $parents) { if (empty($parents)) { $parents = null ; } else { $parents = explode(',', $parents) ; } $this->addRole(new Zend_Acl_Role($role), $parents); } return $this ; } /** * _setResources * * Añade los Recursos al Zend_Acl * * @param Zend_Config * @return Zend_Acl */ protected function _setResources($resources) { foreach ($resources as $resource=>$parent) { $this->add(new Zend_Acl_Resource($resource)); } return $this ; } /** * _setSubresources * * Añade los Subrecursos al Zend_Acl, por debajo de los Recursos * * @param Zend_Config * @return Zend_Acl */ protected function _setSubresources($subresources) { foreach ($subresources as $subresource => $resource) { $this->add(new Zend_Acl_Resource($subresource), $resource); } return $this ; } /** * _setPrivileges * * Añade los Privilegios al Zend_Acl * * @param Zend_Config * @return Zend_Acl */ protected function _setPrivileges($role, $privileges) { // Por cada privilegio foreach ($privileges as $do => $resources) { // Si no tiene Recursos, es un privilegio global if (empty($resources)) { $this->{$do}($role); } else { // Por cada Recurso foreach ($resources as $resources => $actions) { // Si no tiene acciones if (empty($actions)) { $actions = null ; } else { $actions = explode(',', $actions) ; } $this->{$do}($role, $resources, $actions); } } } return $this ; } } |
Ahora solamente hay que utilizar esta clase, y siguiendo la estructura de Zend_Auth que ya teníamos, haremos unas pequeñas modificaciones en la clase Plugin_CheckAccess para que compruebe la autorización después de comprobar que el usuario ya esta identificado.
Para no poner todas las modificaciones que hay que hacer respecto a la versión anterior, pego todo el nuevo código. Solo quiero hacer unos comentarios:
- $_acl : nuevo atributo que contiene el Zend_Acl
- En el constructor se instancia el Zend_Acl en $_acl
- getRol() : función que devuelve el rol almacenado por Zend_Auth en el momento de la autentificación
- En cada petición, se comprueba el controlador y la acción en el Zend_Acl para comprobar que tiene privilegios. Sinó, es redirigido hacia el controlador de Error
- isAllowed() : función para comprobar si tiene privilegios en un recurso, o en un permiso de un recurso. Esta función es la que será llamada en el controlador para hacer más comprobaciones.
<?php class Plugin_CheckAccess extends Zend_Controller_Plugin_Abstract { /** * Contiene el objeto Zend_Auth * * @var Zend_Auth */ private $_auth; /** * Contiene el objeto Zend_Acl * * @var Zend_Acl */ private $_acl; /** * El objeto de la clase singleton * * @var Plugin_CheckAccess */ static private $instance = NULL; /** * Constructor */ private function __construct() { $this->_auth = Zend_Auth::getInstance(); $this->_acl = new My_Permission_Acl(APPLICATION_PATH."/configs/permissions.ini"); } /** * Devuelve el objeto de la clase singleton * * @return Plugin_CheckAccess */ static public function getInstance() { if (self::$instance == NULL) { self::$instance = new Plugin_CheckAccess (); } return self::$instance; } /** * Retorna el Rol del usuario actual * * @return string */ private function getRol() { return ($this->_auth->hasIdentity()) ? $this->_auth->getIdentity()->rol : 'invitado'; } /** * preDispatch * * Funcion que se ejecuta antes de que lo haga el FrontController * * @param Zend_Controller_Request_Abstract $request Peticion HTTP realizada * @return * @uses Zend_Auth */ public function preDispatch(Zend_Controller_Request_Abstract $request) { $controllerName = $request->getControllerName(); $actionName = $this->getRequest()->getActionName(); // Si el usuario esta autentificado if ($this->_auth->hasIdentity()) { // Si tiene autorización para el controlador if (!$this->isAllowed( $controllerName, $actionName) ) { // Mostramos el error de que no tiene permisos $request->setControllerName("error"); $request->setActionName("deniedpermission"); } } else { // El usuario no esta autentificado // Si el Usuario no esta identificado y no se dirige a la página de Login if ($controllerName != 'login') { // Mostramos al usuario el Formulario de Login $request->setControllerName("login"); $request->setActionName("index"); } } } /** * isAllowed * * Retorna si tiene los permisos necesarios para el recurso y el permiso * solicitado * * @param string $resource * @param string $permission optional * @return bool */ public function isAllowed ($resource, $permission = null) { // Por defecto, no tiene permisos $allow = false; // Si solo pregunta por el recurso if (is_null($permission)) { $allow = $this->_acl->isAllowed($this->getRol(), $resource); } // Si pregunta por el recurso y el permiso else { $allow = $this->_acl->isAllowed($this->getRol(), $resource, $permission); } return $allow; } } |
Un ejemplo de archivo INI para cargar la configuración de permisos sería el siguiente. Hay que definir los Roles, los Recursos y los Subrecursos; y después, los privilegios por cada Rol:
[roles] collaborator = null editor = "collaborator" commercial = null admin = null [resources] basicos = null contenidos = null marketing = null administracion = null [subresources] ;BASICOS index = "basicos" error = "basicos" ;CONTENIDOS contents = "contenidos" categories = "contenidos" ;MARKETING ads = "marketing" newsletters = "marketing" ;ADMINISTRACION users = "administracion" [collaborator] allow.basicos = null allow.contenidos = null deny.contents = "active" [editor] allow.contents = null [commercial] allow.marketing = null [admin] allow = null |
Para acabar esta entrada tan larga, solo quiero mostrar el código necesario para hacer una comprobación de privilegios cuando nos encontramos en el controlador:
if (Plugin_CheckAccess::getInstance()->isAllowed('contents','active')) { ... } |
Lo único que queda pendiente es aplicar un sistema de excepciones para los casos en que se produce un error, como por ejemplo, cuando se pregunta si tiene privilegios en un recurso que no ha sido aplicado a Zend_Acl.
- Tags: php, zend framework, zend_acl, zend_auth


11 Comentarios en Zend_Acl: autorización y permisos en Zend Framework
Daniel
10 noviembre 2009 a las 23:02
Sobre lo ultimo que comentas, segun tengo entendido, cuando un recurso no ha sido aplicado a Zend_Acl, funciona como si no tuviera permiso.
otroblogmas.com
11 noviembre 2009 a las 01:06
Efectivamente, por defecto todo esta prohibido, hay que dar privilegios explicitamente.
Lucas
29 agosto 2010 a las 14:04
Gracias, la verdad que muy bueno, cuesta un poco entender las asignaciones por que lo hace sobre los ciclos for , y al principio no te das cuenta bien que esta asignado, pero bien
saludos
http://www.ajaxshake.com
Manuel
30 noviembre 2011 a las 11:20
Me da un fallo al regustrar el plugin ya que el contructor es una funcion privada. Tiene sentido que sea privada?
Francisco Andrés Pinzón SantoYo
21 febrero 2012 a las 23:10
Buenas tardes maestro, sos un duro. Tengo una pregunta matadora, quizás la respuesta lo sea más aún.
Sucede que tengo un sitio modularizado, como cuaqueir sitio, sucede que la idea es que cualquier matacho vea la página sin problema, como ver un portal cualqueira, sin necesidad de logueo (si no, que gracia), pero para ciertas funciones elementales o ciertas áreas del sitio si necesito loguear a la persona y permitirle o no ciertas acciones, si, eso lo hace Acl, pero no logro ahcer que el sitio sea visible sin resticción pa cualquier ser humano, salvo como mencioné, manito ayudame pués, gracias de antemano.
jor
05 marzo 2012 a las 14:44
hola
estoy buscando curso de zend de casualidad no sabes puedo tomar cursos en linea
mil gracias
alejo
10 marzo 2012 a las 23:57
Hola muy bueno el tutorial de casualidad no tendras el ejemplo completo para poder ver mejorvy entender apenas estoy iniciado con esto de roles, mi email es alsaca79@hotmaiil.com muchas gracias
kicke
25 abril 2012 a las 17:43
hola
comparto la idea con alejo,
mi correo es carlitos.skunk@gmail.com
de antemano muchas gracias otroblogmas.com
Yesid Davila
27 octubre 2012 a las 23:11
Buenas tardes amigo, esta bueno tu ejemplo, pero tengo un problema, la cosa es que yo no se como cargar la libreria MY, desde el bootstrap.
Por favor ilustreme con un ejemplo, se lo agradeceria…
Armando
07 noviembre 2012 a las 22:02
Hola, buenas tardes me puedas ayudar al respecto de como podria hacer para armar un acl de acorde a mi escenario.
ALexander
08 noviembre 2012 a las 18:16
Hola chicos explicar por aqui es muy dificil mejor coloquen el email les mando un ejemplo real de como se crea acl con Bd (bases de datos)
de esta manera pueden darle permisos aun un usuario para que utilise ciertas partes de su aplicacion segun el rol o segun el usuario.
mi email es asalas79@gmail.com
de esta manera les mando un ejemplo y me pregunta explicacion
Saludes y no se desanimen que todos pasamo por esto, luego ya entendemos y decimo que facil…. asi es todo en la vida.