Introducción al control de acceso basado en roles (RBAC)

Todos los que hemos empezado a utilizar Yii para crear aplicaciones Web, nos hemos dado de bruces cuando hemos intentado poner a funcionar el RBAC (acceso de control basado en roles).

Unos por no tener conocimientos de como configurarlo, y otros porque una vez configurado correctamente no entiende como funciona realmente.

Por ello, he creído necesario exponer mis escasos, pero efectivos, conocimientos referentes al tema.

En primera instancia, no cabe duda que todos habéis leido el apartado Role-Based Access Control de la guía definitiva de Yii. Sí, está en inglés. Existe una traducción que hice en el FORO por si queréis leerla.

Creerme cuado os digo, que con sólo una lectura no basta para entender los conceptos expuestos. Hay que experimentar para enterder los conceptos (como siempre).

Una vez que hayáis leído (n veces) el apartado Role-Based Access Control, pasamos a la configuración del RBAC.

Como se configura RBAC.

Como habréis leído existen dos maneras de configurar RABC, una de ellas es utilizando un script PHP para almacenar los datos, y, creo que la más `popular, utilizando una base de datos para almacenarlos.

En esta pequeña guía vamos a basarnos en la opción de base de datos.

Bien, partimos de que todos conoceis, aunque sea un poco, la estructura de directorios de vuestra aplicación.

Por si todavía no la conocéis, en el directorio "protected" existe un directorio llamado "config" (este es el que os interesa de momento), en él encontraréis un archivo cuyo nombre es "main.php". Abrirlo con el editor que uséis normalmente.

Este archivo lo modificasteis para activar la configuración de la base de datos. Y si todavía no lo habéis hecho, no os preocupéis, aun estáis a tiempo.

Dependendiendo de si utilizáis SQLite o MySQL debereis comentar o descomentar parte de código (la experiencia me ha dicho, que jamas borre código de un archivo que se me ha proporcionado, no estoy didiendo que hagáis lo que yo hago, sólo os doy un consejo):

Este bloque de código es para activar el uso de SQLite:

'db'=>array(
		'connectionString' => 'sqlite:'.dirname(__FILE__).'/../data/testdrive.db',
	),

Y este otro, es para el uso de MySQL:

'db'=>array(
		'connectionString' => 'mysql:host=localhost;dbname=mibasededatos',
		'emulatePrepare' => true,
		'username' => 'usuario',
		'password' => 'contrasenia',
		'charset' => 'utf8',
	),

Para los que aun no hayan configurado el acceso a base de datos, pueden hacerlo ahora (comentando la parte de código que no va a usar y descomentando el código que va a utilizar).

En este archivo colocaremos el código que se muestra a continuación, dicho código será el encargado, por decirlo de algún modo, de activar el RBAC.

'authManager'=>array(
        'class'=>'CDbAuthManager',
        'connectionID'=>'db',
    ),

Ahora os queda la tarea de crear las tablas donde se almacenarán los datos. Dentro del framework en el directorio "web" encontraréis un directorio con el nombre "auth", en dicho directorio existe un archivo llamado "schema.sql", el cual contiene un script SQL que creara las tres tablas necesarias.

Para que no perdáis el tiempo en buscar el script, os lo coloco a continuación:

[sql]
/**
 * Database schema required by CDbAuthManager.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.yiiframework.com/
 * @copyright Copyright &copy; 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 * @since 1.0
 */

drop table if exists AuthAssignment;
drop table if exists AuthItemChild;
drop table if exists AuthItem;

create table AuthItem
(
   name                 varchar(64) not null,
   type                 integer not null,
   description          text,
   bizrule              text,
   data                 text,
   primary key (name)
);

create table AuthItemChild
(
   parent               varchar(64) not null,
   child                varchar(64) not null,
   primary key (parent,child),
   foreign key (parent) references AuthItem (name) on delete cascade on update cascade,
   foreign key (child) references AuthItem (name) on delete cascade on update cascade
);

create table AuthAssignment
(
   itemname             varchar(64) not null,
   userid               varchar(64) not null,
   bizrule              text,
   data                 text,
   primary key (itemname,userid),
   foreign key (itemname) references AuthItem (name) on delete cascade on update cascade
);

Creo que todos sabréis ejecutar este escript, con lo que me salto este proceso.

Bien, ya tenemos todo lo necesario para empezar a utilizar el RBAC.

Para aquellos que os preguntáis si se pueden cambiar los nombres de las tablas y los campos, deciros que sí, pero ojo, cambiar estos nombres conlleva cambiar partes de código.

Cambiar los nombres de las tablas no conlleva mucho trabajo extra, pero el cambio de los nombres de los campos os llevará mucho trabajo.

Para los que queráis cambiar el nombre de las tablas, simplemente añadir tres líneas más en el código donde se activaba el RBAC. Tener en cuenta que los nombres de las tablas creadas deben de coincidir con el nombre de las tablas en la configuración:

'authManager'=>array(
        'class'=>'CDbAuthManager',
        'connectionID'=>'db',
		'itemTable'=>'AuthItem', // Tabla que contiene los elementos de autorizacion
		'itemChildTable'=>'AuthItemChild', // Tabla que contiene los elementos padre-hijo
		'assignmentTable'=>'AuthAssignment', // Tabla que contiene la signacion usuario-autorizacion
    ),

Cambiar el nombre de los campos, es algo más complicado y no voy a entrar en cómo llevarlo a cabo.

Como funciona RBAC.

Bueno, ahora toca un poco de teoría. ¿Recordáis que elementos de autorización existen? No os preocupéis, si no los recordáis, pero será necesario que os lo vayáis aprendiendo: roles, tareas y operaciones.

En la guía se dice que un rol consiste en tareas, y estas consisten de operaciones. Pues bien, eso es teoría. Realmente no existe una diferencia clara entre roles, tareas y operaciones. Si, si, que en la teoría dicen que son cosas diferentes, pero en realidad son conceptos diferentes de una misma cosa. La diferenciación viene a explicar que se pueden crear elementos de autorización dependientes de otros elementos de autorización. Pero como bien dice la guía son elementos de autorización, se llamen como se llamen, eso es lo que nos interesa. Elemento de autorización.

Una vez entendido esto, podeis crear la jerarquía que necesitéis. En algunas aplicaciones puede bastar con tener solamente roles, sin necesidad de crear autorizaciones detalladas (tareas y operaciones), por ejemplo distinguir entre administradores y usuarios normales. Cuando vayais a diseñar vuestro esquema jerarquizado, tener bien claro que necesidades teneis.

Como ya se ha dicho, existe la posibilidad de crear tareas para un rol, operaciones de una tarea, e incluso operaciones de un rol (sin necesidad de tener tareas). Esto vendría a ser un esquema en forma de árbol, pero Yii permite que un rol sea parte de otro rol, que una tarea sea parte de otra tarea, y que una operación sea parte de otra operación, con lo que la forma de árbol deja de existir. Este esquema jerarquizado se denomina en la guía como una jerarquía de autorización de orden parcial.

Bien, en dicho esquema se puede decir que hay elementos padre y elementos hijo. Así que habrá herencia de autorización. Pero ¿quién hereda de quién? La lógica y el sentido común diría que los hijos heredan del padre, pero en nuestro caso es el padre el que hereda de los hijos. Suena raro, pero veamos un ejemplo y lo veréis claramente.

Nota: No voy a entrar en detalles de como generar elementos de autenticación, ni como asignarlos a usuarios mediante código, los ejemplos aquí expuestos se pueden realizar introduciendo los datos directamente en las tablas correspondientes. Cuando vayáis hacer uso de código para crear los elementos y las asignaciones, la inserción en las tablas se hace automáticamente. Lo que teneis que tener encuenta son dos cosas importantes:

  1. Los tipos de elementos son numéricos 2-Rol, 1-Tarea, 0-Operación.

  2. Los IDUsuario, no corresponde con la clave primaria de la tabla de Usuarios, sino con el nombre que el usuario ha utilizado para acceder a la aplicación.

Supongamos que tenemos dos usarios, cada uno de ellos con un elemento de autorización asignado, pero dichos elementos de autorización son padre e hijo:

Nombre Elemento | Tipo Elemento
--------------------------------
administrador   | 2 (Rol)
adminFinanciero | 1 (Operación)

Elemento Padre  | Elemento Hijo
---------------------------------
administrador   | adminFinanciero

IDUsuario  |  Elemento
-----------------------------
Usuario1   |  administrador
Usuario2   |  adminFinanciero

Y queremos controlar la autorización en la acción de mostrar un Cliente, veamos como se comporta cada uno de los usuarios dependiendo de que comprobación hagamos:

public function actionView($id)
{
	if (Yii::app()->user->checkAccess("administrador"))
	{
		echo 'HOLA';
	} else {
		echo 'ADIOS';
	}
}

Si el usuario activo es Usuario1 mostraría: HOLA

Si el usuario activo es Usuario2 mostraría: ADIOS

public function actionView($id)
{
	if (Yii::app()->user->checkAccess("adminFinanciero"))
	{
		echo 'HOLA';
	} else {
		echo 'ADIOS';
	}
}

Si el usuario activo es Usuario1 mostraría: HOLA

Si el usuario activo es Usuario2 mostraría: HOLA

Recordar que hemos dicho que el padre hereda del hijo. Podríamos decirlo de otra manera, el padre es capaz de hacer todo lo que hacen sus hijos. O de otra manera, el elemento padre delega en los elementos hijos, pero no pierde la virtud de la delegación.

Espero que cualquiera de las anteriores definiciones os aclaren como funciona la herencia en RBAC.

Una vez aclarada la herencia pasemos a explicar dónde se debe de comprobar el acceso. Bueno, pues puede ser en el controlador o en la vista. Depende de que queramos hacer, si lo que queremos, por ejemplo, es mostrar u ocultar opciones de un menú, está claro que debe ser en la vista. Pero si lo que queremos es controlar las acciones, deberemos utilizar el controlador, valga la redundancia.

Vale, algunos de vosotros os preguntaréis, ¿tenemos que escribir los condicionales de los ejemplos anteriores en todas las acciones del controlador? Pues depende de que quereis hacer, si lo que buscáis es que si tiene permiso ejecute la acción y si no tiene permiso deniegue la acción, lo más simple es hacer la comprobación en la "accessRules" del controlador. Si eso es lo que necesitáis, a continuación tenéis un ejemplo:

public function accessRules()
	{
		return array(
			array('allow',
				'actions'=>array('index'),
				'users'=>array('?'),
			),
			array('allow',
				'actions'=>array('view'),
				'users'=>array('@'),
			),
			array('allow',
				'actions'=>array('create','update','admin','delete'),

				'expression'=>'Yii::app()->user->checkAccess("adminFinanciero")',
			    // or
			    // 'roles'=>array('adminFinanciero'),

			),
			array('deny',
				'users'=>array('*'),
			),
		);
	}

Veamos el ejemplo con detenimiento. A los usuarios no logueados (visitantes) se les permite la acción INDEX y se deniegan todas las demás, a los logueados (usuarios registrados) se les permite las acciones INDEX y VIEW, mientras que se les deniega el resto de acciones. A los usuarios que tengan el elemento de autenticación "adminFinanciero" asignado (directa o indirectamente) tendrán acceso a las acciones INDEX, VIEW, CREATE, UPDATE, ADMIN y DELETE; y se les deniega el resto de acciones (si las hubiera).

Pues con esto, voy a dar por terminado este pequeño artículo introductorio al RBAC.

Espero que sirva a más de uno para aclarar ideas, pero con que le sirva a uno sólo me doy por satisfecho.

Nos vemos en otros artículos.

15 1
9 followers
Viewed: 73 272 times
Version: 1.1
Category: Tutorials
Written by: Juan Calvo
Last updated by: Juan Calvo
Created on: Dec 14, 2010
Last updated: 13 years ago
Update Article

Revisions

View all history

Related Articles