Zend_Acl: autorización y permisos en Zend Framework

In: zend framework

7 oct 2009

zend framework Zend Acl: autorización y permisos en Zend Framework

Una de las varias APIs que ofrece es , 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.

Entradas relacionadas:

  1. Zend_Auth: Identificación y autentificación en Zend Framework
  2. Atajos de teclado de Netbeans para Zend Framework
  3. Configurar PHPUnit y Zend Framework
  4. Configurar Netbeans para trabajar con PHPUnit y Zend Framework
  5. Instalar Zend Framework y Zend_Tool para construir proyectos en Windows

4 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?

Formulario de Comentario

Página 1 de 11