A free and open-source book on ZF3 for beginners

Translation into this language is not yet finished. You can help this project by translating the chapters and contributing your changes.

17.10. Implementar el RbacManager

La siguiente cosa que discutiremos será la funcionalidad para crear el contenedor Rbac, cuyo propósito es cargar la jerarquía de roles desde la base de datos y guardarla en el sistema de archivos como caché.

La caché permite guardar los datos usados con frecuencia en un almacenamiento rápido. Por ejemplo, recuperar los roles y permisos desde la base de datos para cada página que se carga puede ser lento, mientras que guardar la jerarquía de roles precomputada en un archivo puede ser más rápido.

17.10.1. Configurar la caché

Primero, vamos a configurar la memoria cache. Para hacer esto, necesitamos instalar los componentes Zend\Cache y Zend\Serializer con los siguientes comandos:

php composer.phar require zendframework/zend-cache
php composer.phar require zendframework/zend-serializer

Finalmente, editar el archivo config/autoload/global.php y agregar las siguientes líneas:

use Zend\Cache\Storage\Adapter\Filesystem;

return [
    //...
    // Cache configuration.
    'caches' => [
        'FilesystemCache' => [
            'adapter' => [
                'name'    => Filesystem::class,
                'options' => [
                    // Store cached data in this directory.
                    'cache_dir' => './data/cache',
                    // Store cached data for 1 hour.
                    'ttl' => 60*60*1
                ],
            ],
            'plugins' => [
                [
                    'name' => 'serializer',
                    'options' => [
                    ],
                ],
            ],
        ],
    ],
    //...
];

Esto permitirá usar la caché Filesystem y guardar los datos en la carpeta APP_DIR/data/cache.

Si queremos aprender más sobre la caché, podemos ver la documentación del componente de ZF3 Zend\Cache.

17.10.2. Escribir el servicio RbacManager

El propósito del servicio RbacManager será construir el contenedor Rbac y cargar los roles y permisos desde la base de datos. Si la información necesaria ya está guardada en cache, esta se cargará desde la caché en lugar que desde la base de datos.

Otro objetivo del servicio RbacManager será usar el gestor de aserciones que escribimos antes para revisar dinámicamente las aserciones.

La clase RbacManager tendrá dos métodos:

La clase RbacManager leerá la configuración y buscará la llave rbac_container. La llave deberá contener la subllave assertions, en la que podemos registrar todas las aserciones que queramos.

return [
    //...

    // This key stores configuration for RBAC manager.
    'rbac_manager' => [
        'assertions' => [Service\RbacAssertionManager::class],
    ],
];

El código de la clase RbacManager que está en el espacio de nombres User\Service se muestra abajo.

<?php
namespace User\Service;

use Zend\Permissions\Rbac\Rbac;
use Zend\Permissions\Rbac\Role as RbacRole;
use User\Entity\User;
use User\Entity\Role;
use User\Entity\Permission;

/**
 * This service is responsible for initialzing RBAC (Role-Based Access Control).
 */
class RbacManager
{
    /**
     * Doctrine entity manager.
     * @var Doctrine\ORM\EntityManager
     */
    private $entityManager;

    /**
     * RBAC service.
     * @var Zend\Permissions\Rbac\Rbac
     */
    private $rbac;

    /**
     * Auth service.
     * @var Zend\Authentication\AuthenticationService
     */
    private $authService;

    /**
     * Filesystem cache.
     * @var Zend\Cache\Storage\StorageInterface
     */
    private $cache;

    /**
     * Assertion managers.
     * @var array
     */
    private $assertionManagers = [];

    /**
     * Constructs the service.
     */
    public function __construct($entityManager, $authService, $cache, $assertionManagers)
    {
        $this->entityManager = $entityManager;
        $this->authService = $authService;
        $this->cache = $cache;
        $this->assertionManagers = $assertionManagers;
    }

    /**
     * Initializes the RBAC container.
     */
    public function init($forceCreate = false)
    {
        if ($this->rbac!=null && !$forceCreate) {
            // Already initialized; do nothing.
            return;
        }

        // If user wants us to reinit RBAC container, clear cache now.
        if ($forceCreate) {
            $this->cache->removeItem('rbac_container');
        }

        // Try to load Rbac container from cache.
        $this->rbac = $this->cache->getItem('rbac_container', $result);
        if (!$result)
        {
            // Create Rbac container.
            $rbac = new Rbac();
            $this->rbac = $rbac;

            // Construct role hierarchy by loading roles and permissions from database.

            $rbac->setCreateMissingRoles(true);

            $roles = $this->entityManager->getRepository(Role::class)
                    ->findBy([], ['id'=>'ASC']);
            foreach ($roles as $role) {

                $roleName = $role->getName();

                $parentRoleNames = [];
                foreach ($role->getParentRoles() as $parentRole) {
                    $parentRoleNames[] = $parentRole->getName();
                }

                $rbac->addRole($roleName, $parentRoleNames);

                foreach ($role->getPermissions() as $permission) {
                    $rbac->getRole($roleName)->addPermission($permission->getName());
                }
            }

            // Save Rbac container to cache.
            $this->cache->setItem('rbac_container', $rbac);
        }
    }

    /**
     * Checks whether the given user has permission.
     * @param User|null $user
     * @param string $permission
     * @param array|null $params
     */
    public function isGranted($user, $permission, $params = null)
    {
        if ($this->rbac==null) {
            $this->init();
        }

        if ($user==null) {

            $identity = $this->authService->getIdentity();
            if ($identity==null) {
                return false;
            }

            $user = $this->entityManager->getRepository(User::class)
                    ->findOneByEmail($identity);
            if ($user==null) {
                // Oops.. the identity presents in session, but there is no such user in database.
                // We throw an exception, because this is a possible security problem.
                throw new \Exception('There is no user with such identity');
            }
        }

        $roles = $user->getRoles();

        foreach ($roles as $role) {
            if ($this->rbac->isGranted($role->getName(), $permission)) {

                if ($params==null)
                    return true;

                foreach ($this->assertionManagers as $assertionManager) {
                    if ($assertionManager->assert($this->rbac, $permission, $params))
                        return true;
                }
            }
            
            $parentRoles = $role->getParentRoles();
            foreach ($parentRoles as $parentRole) {
                if ($this->rbac->isGranted($parentRole->getName(), $permission)) {
                    return true;
                }
            }
        }

        return false;
    }
}

La fábrica para la clase RbacManager tiene el siguiente aspecto:

<?php
namespace User\Service\Factory;

use Interop\Container\ContainerInterface;
use User\Service\RbacManager;
use Zend\Authentication\AuthenticationService;

/**
 * This is the factory class for RbacManager service. The purpose of the factory
 * is to instantiate the service and pass it dependencies (inject dependencies).
 */
class RbacManagerFactory
{
    /**
     * This method creates the RbacManager service and returns its instance.
     */
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $entityManager = $container->get('doctrine.entitymanager.orm_default');
        $authService = $container->get(\Zend\Authentication\AuthenticationService::class);
        $cache = $container->get('FilesystemCache');

        $assertionManagers = [];
        $config = $container->get('Config');
        if (isset($config['rbac_manager']['assertions'])) {
            foreach ($config['rbac_manager']['assertions'] as $serviceName) {
                $assertionManagers[$serviceName] = $container->get($serviceName);
            }
        }

        return new RbacManager($entityManager, $authService, $cache, $assertionManagers);
    }
}

Top