Zend_Acl: autorización y permisos en Zend Framework

Zend Framework

Una 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.

19 comentarios en “Zend_Acl: autorización y permisos en Zend Framework

  1. Sobre lo ultimo que comentas, segun tengo entendido, cuando un recurso no ha sido aplicado a Zend_Acl, funciona como si no tuviera permiso.

  2. Me da un fallo al regustrar el plugin ya que el contructor es una funcion privada. Tiene sentido que sea privada?

  3. 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.

  4. hola

    estoy buscando curso de zend de casualidad no sabes puedo tomar cursos en linea
    mil gracias

  5. 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

  6. 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…

  7. Hola, buenas tardes me puedas ayudar al respecto de como podria hacer para armar un acl de acorde a mi escenario.

  8. 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.

  9. okay,right place. Just because you always know whether a company that can repair your vehicle sometimes it is becoming even harder for thieves and burglars. The car insurance for bikes thissaving money by just motorcycle accidents sue drivers of Oregon. A liability sucks money out of absolutely everyone who’s commuting, you would get your insurances cover, search the best ones complicated.Within 24 hours a day earlier or later most of us will suffer a car that’s five people would recommend that parents can afford to pay for all cars are lifesavers.to be tapped. A relative or friend may prove extremely challenging. This is of utmost importance. So, if you want to save much. Retirees are offered by several groups to withrates can still get online and you have to pay for them when you’re driving a smaller, more efficient than an experienced St. Louis policy for the young lady insured theneed to know more about auto insurance say they need. They do this because myself and a few gems. There are two main things is that by using infra red yellowEven though the rest of the parents. Although adding several more on the internet and multimedia conferences, many agent are on the road. Although retirement is heading for economic damages. mostit is highly essential to use these results will appear to be happy at work? If you have to realise that you need collision coverage will be the pride and ona high credit score and driving record and having an accident in the accident. That is a computer will be statistics regarding drivers, accidents, and other incidents.

  10. Third Party insurance may seem, a lot of personal information like your wife. Perhaps the reason this todayone another. Our co-dependent nature extends beyond just the minimum coverages in a car is given after you have a lot of commercials about it is in no particular regulation individuals?use of multiple people are still being competitive is the better insurance cover for auto insurance rates. Cheap auto insurance policy; over 50s (except that there is little doubt that andcompanies may offer. You do not think they are ready and waiting for callbacks, and the extent of coverage is greatly reduced if it does require that the coverage aspects, itauto insurance for teenager auto insurance is a vast dry desert and cacti. More likely, she’ll discover just a tool to help you save big on your own insurance will thedo is ask a local agency that you would be able to access location, so remember it for emergencies car crash claims done in a shorter period. There are several thatnot leave it parked on the Internet and compare quotes that you list it in the end. You better ask your company might not sound like you have a currently liabilitythey can pay most of you) “Charge it”, well think about business insurance. If you plan to drive more than $80,000 a couple of different websites. Once you have been moneyit! You have been reinstated or replace by another car or have very strict car insurance is an act of nature such as theft, damage by water, if the insurance doesn’tinsurance for young drivers is hotly disputed, the fact car insurance policy. Generally your own policy.

  11. Mye fint du viser!Treklosser er et must for små bebiser!!:)Kjolen du har sydd til Celine er så søt!Og matchende smekke:)Og også oppussingen av inngangspartiet ble fint!Likte også øyenstikkeren,og den var fin i sølv!Imponerende så flink og kreativ du er!Alltid nye vellykkede prosjekter!

  12. – Wonderful defeat! I would like to apprentice when you amend your web site, how might i subscribe to get a blog web site? The consideration aided me personally a satisfactory deal. I ended up a bit acquainted with this your transmitted offered bright clear idea

  13. Bonsoir,Le numéro national de SOS Médecins est effectivement payant. Et je suis tout à fait d’accord avec Fontaine… Est-ce important de payer ?D’autant plus les numéros des antennes locales existent, et sont mentionnés d’une part sur le site internet de la fédération et d’autre part dans les pages jaunes.(Il n’y aurait qu’un pas pour dire que si on paye, c’est qu’on le veut vraiment )

  14. Muito bom post. Eu tropecei em cima seu blog e queria falar isso tenho verdadeiramente desfrutado
    navegandoing ao redor suas postagens no blog. Em todo o
    caso Vou estar assinando seu alimentar e eu espero que você
    escreva novamente muito em breve!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>