Phalcon ACL en Base de datos

Phalcon permite incorporar un sistema de control de accesos (ACL) de forma sencilla. Es cierto que no viene con la instalación por defecto de Phalcon, pero bastará con añadir Incubator a nuestro proyecto y ya podremos utilizarlo. Además, tendremos acceso a cientos de funcionalidades extras desarrolladas por terceros.

Configuración

Lo primero que necesitamos para poder usar ACL con nuestro framework es añadir phalcon-incubator  a nuestro composer:

"require": {
    "phalcon/incubator": "dev-master"
}

Posteriormente, necesitamos crear en nuestra base de datos las tablas para poder gestionar el sistema de permisos:

  • phalcon_roles: Contiene los roles disponibles donde cada rol tendrá un determinado número de permisos.
  • phalcon_access_list: Contiene la relación entre roles, recursos y accesos y si tiene permisos o no
  • phalcon_resources: Recursos disponibles (controllers)
  • phalcon_resources_accesses: Relación de recursos y accesos (actions)
  • phalcon_roles_inherits: Establece la relación de jerarquía entre roles
CREATE TABLE phalcon_roles (name varchar(32) COLLATE utf8_unicode_ci NOT NULL, description text, primary key(name));
CREATE TABLE phalcon_access_list (roles_name varchar(32) COLLATE utf8_unicode_ci not null, resources_name varchar(32) COLLATE utf8_unicode_ci not null, access_name varchar(32) COLLATE utf8_unicode_ci not null, allowed int(3) not null, primary key(roles_name, resources_name, access_name));
CREATE TABLE phalcon_resources (name varchar(32) COLLATE utf8_unicode_ci not null, description text COLLATE utf8_unicode_ci, primary key(name));
CREATE TABLE phalcon_resources_accesses (resources_name varchar(32) COLLATE utf8_unicode_ci not null, access_name varchar(32) COLLATE utf8_unicode_ci not null, primary key(resources_name, access_name));
CREATE TABLE phalcon_roles_inherits (roles_name varchar(32) COLLATE utf8_unicode_ci not null, roles_inherit varchar(32) COLLATE utf8_unicode_ci not null, primary key(roles_name, roles_inherit));

Crear roles y permisos

namespace App;

use \Phalcon\Acl;
use \Phalcon\Acl\Adapter\Database as AclDatabase;
use \Phalcon\Acl\Resource;
use \Phalcon\Acl\Role;
use \Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$dbParams = array(
    'dbname'   => $config->connections->default->dbname,
    'username' => $config->connections->default->username,
    'password' => $config->connections->default->password,
    'host'     => $config->connections->default->host
);

$connection = new PdoMysql($dbParams);

$acl = new AclDatabase(array(
    'db' => $connection,
    'roles' => 'phalcon_roles',
    'rolesInherits' => 'phalcon_roles_inherits',
    'resources' => 'phalcon_resources',
    'resourcesAccesses' => 'phalcon_resources_accesses',
    'accessList' => 'phalcon_access_list',
));

$acl->setDefaultAction(Acl::DENY);

$users = array(
    array(
        'role_name' => 'GlobalAdmin',
        'role_description' => 'Administrador global',
        'resources' => array(
            'Admin\Index' => array(
                'index'
            ),
            'Admin\Login' => array(
                'index'
            ),
            'Admin\Pay' => array(
                'index'
            ),
            'Admin\Profile' => array(
                'index'
            ),
        )
    ),
    array(
        'role_name' => 'NormalUser',
        'role_description' => 'Usuario con acceso únicamente al frontend',
        'resources' => array(
            'Front\Index' => array(
                'index', 'contact'
            ),
            'Front\Login' => array(
                'login'
            ),
            'Front\Pay' => array(
                'index'
            ),
            'Front\Profile' => array(
                'index'
            )
        ),
        'inheritance' => 'unknownUser'
    ),
    array(
        'role_name' => 'unknownUser',
        'role_description' => 'Usuario con acceso únicamente al frontend cuando no está logado',
        'resources' => array(
            'Front\Index' => array(
                'index', 'contact'
            ),
            'Front\Login' => array(
                'login'
            ),
            'Front\Signup' => array(
                'index'
            )
        )),
);

foreach ($users as $user) {
    $role = new Role($user['username'], $user['user_description']);
    $acl->addRole($role);

    foreach ($user['resources'] as $resourceName => $actions) {
        $resource = new Resource($resourceName);
        $acl->addResource($resource, $actions);
        $acl->allow($user['role_name'], $resourceName, $actions);
    }
}

Inicilizar ACL al arrancar nuestra aplicación


use \Phalcon\Acl\Adapter\Database as AclDatabase;
use \Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$di = new FactoryDefault();

$di->set('acl', function () use ($config) {
    $dbParams = array(
        'dbname'   => $config->connections->default->name,
        'username' => $config->connections->default->sername,
        'password' => $config->connections->default->password,
        'host'     => $config->connections->default->host
    );

    $connection = new PdoMysql($dbParams);

    $acl = new AclDatabase(array(
        'db' => $connection,
        'roles' => 'phalcon_roles',
        'rolesInherits' => 'phalcon_roles_inherits',
        'resources' => 'phalcon_resources',
        'resourcesAccesses' => 'phalcon_resources_accesses',
        'accessList' => 'phalcon_access_list',
    ));

    $acl->setDefaultAction(\Phalcon\Acl::DENY);

    return $acl;
});

Control de accesos

Y desde nuestro PayController comprobamos si tenemos el acceso permitido a una página o no.


$this->view->setVar('allowedUser', 'acceso denegado');
if ($this->di->get('acl')->isAllowed('NormalUser', 'Admin\Pay', 'index')) {
    $this->view->setVar('allowedUser', 'acceso permitido');
}

Así de sencillo es montar un sistema de control de accesos básico.

Este sistema podríamos mejorarlo encapsulando la gestión de ACL en un plugin de seguridad. También el control de accesos podríamos dispararlo en el evento beforeDispatchLoop, y así nos quedaría todo un poco más ordenado. Y evidentemente podríamos comprobar la función isAllowed basándonos en el action y el resource en uso en ese determinado momento 🙂

Deja un comentario