lunes, 20 de septiembre de 2010

CakePHP y Javascript

En principio, no necesitas hacer nada especial para incorporar código javascript en las vistas de tu aplicación CakePHP, aparte de escribir el código, naturalmente. Simplemente has de insertarlo tal y como lo harías en cualquier documento HTML: utilizando la etiqueta script.


<script type="text/javascript" charset="utf-8">
 // Código
</script>


Por supuesto, en algún momento necesitarás utilizar scripts que se encuentren en archivos .js, convenientemente guardados en la carpeta js del webroot de tu aplicación. Para esta tarea es buena idea recurrir a HtmlHelper en CakePHP 1.3 (o a JavascriptHelper en la versión 1.2), ya que lo enlazará correctamente incluso si tu instalación de Cake está muy personalizada.

Por ejemplo:


<?php echo $this->Html->script('jquery.form', array('inline' => false)); ?>


o bien


<?php echo $javascript->link('ckeditor/ckeditor', NULL, false);  ?>


En estos ejemplos, puedes ver que para llamar al archivo javascript no es necesario incluir la extensión y que si el script se encuentra en una subcarpeta de js, hay que incluirlo en el nombre.

Por otro lado, la opción "inline" = false (en el segundo ejemplo, se correspondería con el tercer parámetro del método link) hace que la llamada a nuestro script se incluya en la cabecera del documento. Más exactamente allí donde hayamos realizado un echo $scripts_for_layout. De no indicar nada o ponerlo en true, el script se incluirá en el lugar de la vista donde realicemos el echo.

Puesto que todo lo que acontece en Javascript está en el lado del cliente, no hay más de lo que debas preocuparte en lo que respecta a la aplicación, exceptuando, claro está, el caso de trabajar con Ajax que veremos dentro de un momento, tras un pequeño inciso.

Los helpers Ajax/Javascript han transmutado en Js

La presencia del JavascriptHelper (y de AjaxHelper) en la versión 1.2 o del JsHelper en la versión 1.3 pueden llevarte a pensar que son de algún modo necesarios para poder trabajar con Javascript en una aplicación CakePHP.

Como decía al principio, esto no es así. Realmente no los necesitas si no quieres usarlos, pero te pueden echar una mano, que para eso están. Estos Helpers están ahí para ayudarte a generar algunos bloques de código más o menos típicos y repetitivos de la manera más sencilla posible.

Con todo, en la versión 1.2 podían resultar hasta un poco estorbo, ya que, el AjaxHelper en particular está basado en la librería Prototype, mientras que mucha gente opta desde hace tiempo por otras como jQuery o MooTools.

A consecuencia de esta constatación, el equipo de CakePHP decidió unificar AjaxHelper y JavascriptHelper en el nuevo JsHelper, con la ventaja añadida de poder definir qué framework Javascript preferimos utilizar. Por otro lado, un par de funciones que eran responsabilidad de JavascriptHelper han pasado a HtmlHelper. En concreto, la JavascriptHelper->link() es ahora HtmlHelper->script(), y JavascriptHelper->codeBlock() pasa a ser HtmlHelper->codeBlock(), lo que tiene bastante lógica.

Ajax

Ajax nos puede plantear algunos problemas particulares, ya que se trata básicamente de Javascript que interacciona con nuestra aplicación en segundo plano. Y, si bien, podemos organizar las cosas de modo que aprovechemos el código, debemos tener en cuenta algunos puntos.

RequestHandler Component

Para empezar, nos interesa recurrir a este componente especializado en gestionar información sobre las peticiones que recibe la aplicación y las respuestas que envía.

En general, suelo ponerlo en AppController, pues me puede interesar su uso en muchos lugares distintos de la aplicación. Aunque para usarlo basta incluirlo en el array de components del controlador que lo necesite.

En el caso que nos ocupa, es decir, procesar peticiones que vienen a través de Ajax, RequestHandler nos permite identificarlas mediante el método isAjax(), que devuelve true en el caso que te puedes imaginar. Además, preselecciona por nosotros el layout Ajax (básicamente un layout vacío que solo mostrará el contenido de la vista que toque).

De este modo, podemos bifurcar una acción para hacer cosas distintas si ha sido solicitada a través de Ajax o no. O también podemos asegurarnos de que una acción sólo se ejecuta si se ha pedido por Ajax.

RequestHandler identifica una petición Ajax cuando viene marcada con una cabecera determinada. Esto puede ser un problema si acción no es solicitada a través del objeto XHttpRequest. Por ejemplo, si se utiliza la técnica de iframes, en principio la petición es indistinguible de una petición estándar. En este caso se pueden llevar a cabo diferentes estrategias, como pueden ser "marcar" de algún modo la petición para interpretarla correctamente en el servidor, o bien tener una acción del controlador específica para procesar esa circunstancia, acción a la que "apuntaría" nuestro iframe.

En cualquier caso, dedicar una o varias acciones a procesar peticiones Ajax puede ser un buen planteamiento aunque depende de los casos y de lo "DRY" que quieras mantener el código.

Ajax y Auth

Otro problema que puede aparecer cuando utilizamos peticiones Ajax es que se produce un problema relacionado con el funcionamiento de AuthComponent y el comportamiento de algunos navegadores.

Si una acción requiere que el usuario esté autentificado, la petición Ajax falla porque Auth no reconoce que el usuario está autentificado y redirige al login.

Para dar como autentificado a un usuario, AuthComponent se basa no sólo en los datos de login almacenado en la sesión, sino que en cada recarga comprueba que la sesión no ha sido "secuestrada" por un nuevo agente de usuario. Si observas el contenido de $_SESSION en una aplicación CakePHP verás una variable Agent que identifica al navegador, junto con las demás variables.

En varios navegadores XHttpRequest genera una nueva identificación de agente lo que provoca que CakePHP considere inválida la información de autentificación almacenada en la sesión y vuelva a solicitar al usuario que se conecte mostrando la vista de login.

Una posible solución es poner en false la variable de configuración "Session.checkAgent" en core.php. También puedes deshabilitar este chequeo de forma temporal en la propia petición sólo si esta se hace a través de Ajax y antes de que Auth entre en acción. En AppController puedes poner:


function beforeFilter() {
    parent::beforeFilter();
    if ($this->RequestHandler->isAjax()) {
        Configure::write('Session.checkAgent', false);
    }
    // debug($this->params);
    $this->Auth->allow('display');
}


Otra técnica consiste en reiniciar a mano la sesión.

Respuestas Ajax

Las respuestas Ajax también pueden necesitar un pequeño ajuste antes de ser enviadas de vuelta al cliente.

Si la respuesta generada y esperada es HTML no tendrás que hacer nada especial, pero si la respuesta es JSON es posible que debas enviar una cabecera adecuada para que el navegador se entere de que se trata de JSON y no de otra cosa. Además, debes evitar que intente descargarlo, cosa que ocurre si envías lo que parece la cabecera propia de JSON: application/json.

En mi caso, la cabecera que ha funcionado es application/javascript.

Para ello, podemos utilizar también RequestHandler, indicando que vamos a enviar Javascript.


$this->RequestHandler->respondAs('js');


(Me surge ahora la dudo de si esto es lo que se considera Literal Javascript).

Debug

En muchos casos, aunque no necesariamente en todos, tendrás que poner el modo de debug de CakePHP a cero para que la respuesta Ajax sea aceptable. De otro modo, la información de debug puede hacer irreconocible la respuesta para el lado del cliente (no suele dar problemas si es una respuesta HTML que vaya a mostrarse en un elmento), pero seguro que los dará si es JSON o XML, que son formatos bien estructurados.

Simplemente, añade la línea


Configure::write('debug',0);


en el código que desarrolla la respuesta Ajax.

En estos casos, lo mejor es que hagas un log con la información que necesites para depuración.

Herramientas

Para comprobar el buen funcionamiento de tu código Javascript asegúrate de instalar FireBug en Firefox o activar las herramientas de Desarrollo en Safari (y otros navegadores basados en Webkit, como Chrome).

En ambos podrás estudiar el funcionamiento del código Javascript (marcando puntos de parada, inspeccionando variables y objetos...), así como las solicitudes y respuestas Ajax, lo cual también te ayudará a comprender cómo funcionan las cosas.

domingo, 19 de septiembre de 2010

Javascript y Ajax para torpes (como yo)

Tengo que confesar que tengo un problema con Javascript. Me cuesta mucho entender este lenguaje y, por tanto, utilizarlo. Claro que, a día de hoy, es ya una herramienta imprescindible.

Gracias a una parte del proyecto en el que estoy ahora mismo trabajando he tenido que empezar a plantearme en serio el uso de javascript en la aplicación y como parte de ese aprendizaje voy a dejar caer por aquí algunas anotaciones al respecto.

Esta primera anotación no es un tutorial, sino más bien una clarificación de conceptos sobre el lenguaje en sí, Ajax y otros temas relacionadas. Así que vamos allá:

Javascript

Para más información consulta la entrada Javascript en Wikipedia.

Javascript es un lenguaje de scripting orientado a objetos que está integrado en navegadores web, de modo que el código reside normalmente (o es incluido desde un archivo externo) en un documento HTML.

Quizá la característica más relevante de Javascript es que el lenguaje nos da acceso a una representación del documento HTML. Esta representación es el DOM (Modelo de Documento-Objeto) y nos permite interactuar de forma directa con los elementos de la página. En otras palabras, podemos obtener sus contenidos y modificarlos.

Parte de las dificultades prácticas de la programación en Javascript residen precisamente en la mayor o menor facilidad que tenemos para acceder a un elemento o grupo de elementos con los que necesitemos trabajar. El objeto document posee el método getElementById() que nos permite obtener un elemento si sabemos su atributo id. Sin embargo, esto a veces no es suficiente, ya que nos puede interesar obtener elementos por su clase, por tipo, o por otra forma de selección.

Esto se puede conseguir en el propio lenguaje escribiendo funciones específicas, lo cual ha dado lugar a que se hayan desarrollado diversos frameworks que complementan el Javascript original.

Frameworks

Para evitar estas dificultades y añadir diversas funcionalidades generales, se han creado frameworks o bibliotecas. Algunos ejemplos son jQuery, MooTools, Prototype...

Uno de los métodos de selección que han aportado estas bibliotecas es el uso de la sintaxis de selectores CSS. Es decir, gracias a estos frameworks podemos hacer selecciones de elementos tal y como lo haríamos en CSS, lo cual nos da acceso a cualquier elemento, o familia de ellos, presente en el documento, de una forma sencilla y reutilizando un conocimiento que ya tenemos.

Además, el uso de estos frameworks proporciona una normalización de acceso a propiedades y métodos que facilita la programación.

XHttpRequest

Una de las adiciones más significativas en Javascript fue la inclusión de la clase XHttpRequest, que proporciona al lenguaje la capacidad de enviar peticiones a servidores a través del protocolo HTTP y utilizar la respuesta recibida.

Estas peticiones se hacen, por así decir, desde "dentro" la página web "programáticamente", sin necesidad de recargarla. Y esto nos lleva a Ajax.

Ajax

Más información sobre Ajax

Ajax es una técnica o conjunto de técnicas que se basan en la comunicación en segundo plano entre una página web y el servidor, enviando o recogiendo información que se utiliza para modificar partes de la propia página.

Ajax presenta varias ventajas, siendo la principal que permite una actualización de contenidos en una página en tiempo real (o cuasi-real) sin necesidad de recargar la totalidad de la página y, por tanto, sin interrumpir la actividad del usuario en la misma.

Así, por ejemplo, es posible proporcionar prestaciones como:


  • Widgets o módulos de contenido que se actualizan en tiempo real, como podría ser un módulo de usuarios conectados, algún tipo de chat, etc.
  • Formularios que realizan cálculos con los datos que va introduciendo el usuario sin que éste tenga que enviarlos (incluso cuando esos cálculos requieran consultas al servidor).
  • Formularios que se autoguardan periódicamente para que el usuario no pierda el contenido introducido.
  • En general, cualquier tipo de interacción con el servidor que deba hacerse sin actividad explícita del usuario y sin recargar la página completa.


Ventajas del uso de Ajax son:


  • No interrumpimos la actividad principal que el usuario está llevando a cabo en la página, ya que la petición y actualización se realizan en segundo plano y sin recargar la totalidad de la página.
  • Ahorramos ancho de banda en las peticiones, ya que la solicitud se hace por una información concreta y se interpreta en el navegador sólo en las partes de la página afectadas, evitando tener que recargar información o contenido que ya estaba en la página.
  • Un comportamiento más ágil de la página y más similar a la experiencia de aplicaciones de escritorio.

La respuesta Ajax

La petición enviada por Ajax nos permite obtener una respuesta del servidor. Lógicamente, nuestra aplicación web tiene que saber recibir la petición y darle una respuesta adecuada. Esta respuesta puede adoptar varios formatos, de los que destacan tres, que se utilizarían según el tipo de manipulación que necesitamos hacer en el lado del cliente:

Si necesitamos actualizar un elemento específico lo mejor sería dar una respuesta HTML, es decir, el servidor obtiene la información y la empaqueta como un fragmento HTML. El código javascript del cliente no tiene más que actualizar la propiedad html del elemento al que vaya dirigida asignándole el contenido de la respuesta. Es la solución ideal para widgets o módulos de página.

Si necesitamos post-procesar la respuesta en la página, bien porque debemos utilizar partes de la misma en distintos elementos, bien porque necesitamos operar de distintas formas con ella, la respuesta adecuada sería en formato JSON. Json es una notación de objetos javascript que puede ser utilizada directamente desde el lenguaje a través de la función eval(), aunque puede utilizarse LJS (Javascript literal) como alternativa si sólo se envían datos, de modo que no se usa eval().

Esto lleva al planteamiento de cuestiones de seguridad, ya que eval() es una coladera para todo tipo de código construido de forma maliciosa, por lo que el uso de JSON en principio debe limitarse a intercambios cliente-servidor "de confianza". En general, no habría problema en entornos de una aplicación web que genera páginas que deben interactuar vía Ajax con el propio servidor.

Por tanto, si la seguridad es una característica crítica, y en particular en interacciones entre servidores (como por ejemplo si expones APIs), la respuesta del servidor debería darse en XML.

Javascript, Ajax y CakePHP lo dejo para la próxima anotación.

miércoles, 11 de agosto de 2010

HTML5 reset

Se trata de todo un paquete completo de archivos html, css y javascript con los cuales iniciar un proyecto desarrollado en HTML 5, compatible con Internet Explorer.

Tiene muy buena pinta para empezar y no parece muy difícil de integrar en CakePHP.

HTML5 Reset

martes, 20 de julio de 2010

Hacer tests de un método que crea un objeto que debemos simular (una alternativa a los partial Mock)

(Editado para corregir un error grave)

Supongamos que tenemos un método en una clase y que dentro de ese método se crea un objeto, el cual debemos simular para hacer el test. Algo así como esto:


public function getFeed($url) {
    $Socket = ClassRegistry::init('HttpSocket');
    $Socket->reset(false);
    $response = $Socket->get($url);
    ...
}


En la documentación de SimpleTest se analiza ese caso y se proponen varias soluciones. Pero en el caso de CakePHP he descubierto una que es sencilla y resuelve el problema de una forma muy eficaz y elegante.

HttpSocket es una clase muy a propósito para ser simulada, ya que requiere conectarse a un servidor y no podemos garantizar que sea posible hacerlo en la situación de test, por lo que lo lógico sería usar la simulación. Pero tal como está escrito el método, el objeto se crea y se utiliza (e incluso se destruye) en el ámbito del propio método. En la interfaz de éste no hay forma de pasar el objeto, como se puede ver, por lo que habría que reescribir el código para poder escribir el test.

ClassRegistry al rescate

Para empezar, tendremos que instanciar el objeto con ClassRegistry en lugar de con el tradicional new object() de PHP. ClassRegistry es una factoría de clases que ofrece algunos servicios interesantes. Además de crear los objetos, mantiene un registro, de modo que si una clase es instanciada varias veces, y no indicamos lo contrario, no crea un objeto nuevo cada vez, sino que devuelve el existente, ahorrando memoria.

Esto se hace con el método init, de esta forma:


$Post = ClassRegistry::init('Post');


Con este método se crea una entrada en el registro que pone la clase Post bajo la clave 'Post', devolviendo un objeto de clase Post por referencia.

Pero ClassRegistry tiene otro método que nos interesa: addObject. Este método nos permite poner en el registro cualquier objeto bajo la clave que deseemos.


ClassRegistry::addObject('Post', $MockedPost);


Para el caso que nos ocupa podemos crear el Mock que necesitamos, instanciarlo y pasarlo a ClassRegistry con la clave de la clase que estamos simulando. La próxima vez que se llame a ClassRegistry::init, devolverá el Mock.

El siguiente bloque de código muestra cómo hacerlo:


Mock::generate('HttpSocket');
$Socket = ClassRegistry::init('MockHttpSocket');
ClassRegistry::addObject('HttpSocket', $Socket);


La primera línea genera la simulación de HttpSocket con el nombre MockHttpSocket.

La segunda línea instancia la clase simulada, no es imprescindible usar ClassRegistry, pero tampoco es mala práctica.

Por último, la tercera línea, hace la magia, añadiendo el objeto que acabamos de instanciar en la clave HttpSocket del registro.

Lo anterior es la preparación. A continuación habría que establecer los valores de respuesta que necesitemos, etc.

Luego vendría la llamada para probar el método:


$result = $this->Feed->getFeed($url);


Este es, de nuevo, el fragmento del método que quería probar:


public function getFeed($url) {
    $Socket = ClassRegistry::init('HttpSocket');
    $Socket->reset(false);
    $response = $Socket->get($url);
    ...
}

Al "inyectar" en ClassRegistry la clase simulada MockHttpSocket bajo la clave HttpSocket consigo que en situación de test se utilice el Mock.

Y todo ello sin tocar el código de la clase.

jueves, 10 de junio de 2010

Simulando objetos con Mock Objects

Entre otras facilidades, la Test Suite de CakePHP utiliza la capacidad de SimpleTest de crear Mock Objects, o lo que es lo mismo, simulaciones de objetos que no hacen nada, pero que reproducen todos los métodos de los objetos simulados y que puedes programar para que tengan un comportamiento determinado que te interese.

Esto es útil cuando quieres probar una clase que usa otras clases. En lugar de emplear las clases reales, utilizamos sus simulaciones. De este modo, nos evitamos las posibles interferencias de su funcionamiento sobre los resultados del test y garantizamos que sólo probamos el código de la clase en cuestión.

Un ejemplo sencillo típico sería tratar de probar un Controller que usa un Component.

Lo que tenemos que hacer son básicamente dos cosas:

  • Crear una simulación o Mock del Component
  • Asociar el Component simulado al Controller

Para crear una simulación de una clase usamos Mock::generate('Clase'). Esto nos crea una clase que podemos instanciar para asociar a la clase testada.

Imagina que quieres probar PostsController, habiendo declarado Auth como Component. El código necesario sería algo así como:


App::import('Controller', 'Posts');
App::import('Component', 'Auth');

class PostsTestCase extends CakeTestCase {
   
    var $Posts;
   
    function startTest() {
        $this->Posts = ClassRegistry::init('PostsController');
        Mock::generate('AuthComponent');
        $this->Posts->Auth = new MockAuthComponent();
    }
}

?>


El código de startTest hace lo siguiente:

En primer lugar, instancia PostsController y lo pone en la variable Posts de PostsTestCase, así lo tenemos disponible en cualquiera de los métodos del test.

Seguidamente, se genera el Mock de AuthComponent.

Finalmente, asociamos el AuthComponent simulado a $this->Posts, de manera que haga el papel que haría el AuthComponent real.

Si fuese necesario, tendríamos que simular o hacer a mano cualquier otra operación que fuese necesaria para reproducir el funcionamiento normal de las clases implicadas.

Los métodos de AuthComponent están simulados en MockAuthComponent, pero no hacen nada. Para que el Mock sea útil necesitaremos indicarle que devuelva ciertos datos al llamar a alguno de sus métodos. Para esto utilizamos el método setReturnValue(), de este modo:


...
$this->Posts->Auth->setReturnValue('user', array('User' => array(...)));
...


La línea anterior le indica al objeto simulado que cuando se llame a su método 'user' devuelva el valor indicado. Gracias a esto podemos simular determinados escenarios que nos interesa probar.

Los parámetros que pueda tener el método son indiferentes en este caso. Sin embargo, es posible indicar a setReturnValue que tenga en cuenta los argumentos del método (esto lo dejaré para otro artículo, pero lo puedes encontrar en la documentación de SimpleTest).

Por ejemplo, ¿qué pasa si no hay un usuario autentificado en la acción edit? Para hacerlo, necesitamos indicarle al Auth simulado, que el método 'user' devuelva false:


...
$this->Posts->Auth->setReturnValue('user', false);
$result = $this->Posts->edit(123);
...


Con el código anterior conseguimos simular que Auth->user no devuelve ningún valor (no hay usuario autentificado) y así probamos cómo responde nuestro método a esa situación. Seguidamente podemos hacer otro test, simulando que sí existe un usuario autentificado.

Si fuese necesario podemos "anidar" objetos simulados. Es decir, si tenemos que simular una clase que contiene otras clases, podemos simular estas y asociarlas.

Para simular propiedades (variables de clase) no tenemos más que asignarles el valor deseado.

Ahora, supón que un método de un objeto simulado es llamado varias veces en el método probado y necesitas que cada vez devuelva un valor distinto. En este caso, debes pasarle primero los valores con setReturnValueAt.

$objeto->setReturnValue('value', false);
$objeto->setReturnValueAt(0, 'value', 'Sample');
$objeto->setReturnValueAt(1, 'value', 'Another sample');


En resumen, utilizando Mock Objects pueden prescindir de las dependencias de la clase probada, lo que hará que tus tests sean más flexibles, fiables y precisos.

miércoles, 9 de junio de 2010

BeforeRedirect

Un recordatorio por si usas el callback beforeRedirect en un Component: recuerda que debe devolver una URL, de otro modo los resultados pueden ser absolutamente desquiciantes.

miércoles, 2 de junio de 2010

Model->displayField en plugins

Al menos en CakePHP 1.3 (me imagino que puede pasar en 1.2) la propiedad displayField de los modelos puede tener un comportamiento un poco errático si te has olvidado de especificar correctamente las relaciones con modelos que están en plugins.

En otras palabras. Si tienes un modelo relacionado con otro que se encuentra en un plugin, no olvides indicarlo "prefijándolo" con el nombre del plugin. Por ejemplo, si tienes un modelo que tiene una relación hasMany con el modelo Item que está en el plugin Contents debes expresarlo así:


var $hasMany = array('Item' => array('className' => 'Contents.Item'));


Si no lo haces, métodos como find('list') no serán capaces de usar correctamente la propiedad displayField del modelo relacionado.

sábado, 15 de mayo de 2010

Yo, la autorización y Cake (II): Entendiendo los sistemas de autorización

Sistemas de permisos "a la Unix"

Una forma de afrontar al problema de la autorización es partir del sistema de permisos Unix. En este sistema, cada recurso tiene asociados varios permisos (lectura, escritura, ejecución) que se asignan a un usuario, a un grupo y al resto del mundo. Es bastante fácil empaquetar estos permisos en un sólo atributos y chequearlos usando operaciones binarias (and, or...).

Puedes incorporar esta información de permisos en la propia tabla del modelo que quieres controlar, o normalizarlo y guardar la información en una tabla de permisos, lo que te permite hacerlo más flexible.

La dificultad puede estar en cómo relacionar esta información de permisos con las acciones de los controladores de tu sistema. Una opción es a través de un mapeado acción-tipo de permiso, o bien creando un permiso específico para cada acción.

En el primer caso, las acciones se agruparían en si son de lectura, escritura o cualquiera de los tipos básicos de permiso que hayas establecido.

En el segundo caso, tendrías que registrar cada acción de un controlador y relacionarla con el recurso y los usuarios con acceso.

El componente ACL de Cake permite una forma de uso que utiliza un sistema de permisos de este estilo.

Sistemas basdos en Listas de Control de Acceso (ACL)

ACL son las siglas de Access Control List. Las listas de control de acceso son, como su nombre indica, listas que relacionan a los sujetos con objetos. Es decir, nos dicen quién tiene acceso a qué en el sistema, o quién tiene prohibido el acceso a qué.

Normalmente, el quién son los usuarios, que pueden estar organizados en grupos. En ese caso, si un grupo tiene ciertos privilegios, sus miembros los heredan gracias a la estructura en árbol de las ACL proporcionadas por CakePHP. Esto nos permite no tener que asignar permisos de forma individual a cada usuario (lo que en una organización grande puede ser un trabajo ímprobo), sino que podemos definirlos con una cantidad mínima de reglas o entradas en la lista de control.

A estos sujetos que Reclaman acceso se les llama técnicamente AROs (Access Request Objects: Objetos que Reclaman Acceso).

El qué son los recursos y objetos de la aplicación. En este caso el concepto es un poco más difuso, ya que los recursos pueden ser de diversos tipos. Por ejemplo, en una aplicación de blogs podemos pensar que los posts son recursos. Pero también son recursos las acciones, o sea, las url de la aplicación a las que acceden los usuarios.

A lo objetos cuyo acceso Controlamos se les llama técnicamente ACOs (Access Controlled Objects: Objetos de Acceso Controlado).

En resumen:

  • ACL: Lista de control de acceso.
  • ARO: Sujeto que reclama acceso, habitualmente usuarios o grupos (pero no necesariamente).
  • ACO: Objetos a los cuales los ACO quieren acceder.

En consecuencia, una ACL es una lista en la que definimos si un ARO puede o no acceder a un ACO. El sistema de autorización consulta esa lista y toma una decisión en función de los datos obtenidos.

Esto es lo que te proporciona CakePHP con el componente ACL.

Sistemas basados en Roles (RBAC)

Sin embargo, las ACL "puras" no son el único sistema de control de autorización. Otra metodología la componen los sistemas de acceso basados en roles.

Un rol define lo que los usuarios que ejercen ese rol pueden hacer en el sistema. Un mismo usuario puede tener varios roles, definidos por el administrador del sistema.

Hay un par de roles que podríamos considerar comunes en la base de todo sistema:

  • self: la capacidad de los usuarios para editar los datos de su propio perfil o cuenta.
  • root: capaz de acceder a cualquier recurso

En cierto modo, se puede pensar en los sistemas basados en roles como en otra forma de interpretar la idea de las listas de control de acceso. La diferencia es que, mientras que las ACL tradicionales suelen responder a la pregunta "¿Puede el usuario U hacer lo que solicita", los sistemas basados en roles responden más bien a "¿Qué puede hacer el usuario U estando aquí?".

Poder hacer esta pregunta y, sobre todo, poder obtener una respuesta es bastante útil. Entre otras cosas, te permite exponer al usuario sólo aquella parte de la aplicación a la que tiene acceso.

Yo, la autorización y Cake (I)

Una de las bases de una aplicación corporativa es un buen sistema de autentificación y de autorización. Este último es uno de los aspectos que me resulta más complicado resolver al desarrollar una aplicación. Hasta ahora mis soluciones en este campo han sido funcionales, pero difíciles de mantener y de escalar.

Conceptualmente son procesos bastante sencillos de entender:

Autentificación es el proceso por el que el sistema determina que un usuario es quien dice ser, o por lo menos que presenta las credenciales correctas. Un sistema de autentificación básicamente toma las credenciales aportadas (habitualmente un usuario y contraseña) y las contrasta con alguna fuente de referencia, que puede ser una tabla de una base de datos, un servidor de autentificación, etc.

Autorización es el proceso por el cual se controla qué puede hacer en el sistema el usuario autentificado, es decir: a qué recursos puede acceder. Un sistema de autorización comprueba si el usuario tiene permiso para realizar las acciones que solicita sobre ciertos recursos disponibles, para lo cual consulta alguna fuente de referencia, como atributos de permisos en los recursos, listas de control de acceso, reglas, etc.

CakePHP proporciona algunas herramientas para gestionar estos procesos y construir nuestros sistemas de autentificación y autorización:


  • Componente Auth. Proporciona una forma sencilla de integrar el control de identidad en nuestro desarrollo, permitiendo también bastante flexibilidad.
  • Componente ACL. Se trata de un sistema genérico que nos permite construir listas de control de acceso jerárquicas y usarlas para comprobar si un usuario tiene capacidad de acceder al recurso que solicita.


El componente Auth funciona muy bien y es muy fácil de integrar en nuestros sistemas. No voy a extenderme en él porque hay buenos tutoriales tanto en el CookBook como en otras fuentes.

Sin embargo, el componente ACL suele ser considerado como más difícil de usar. Creo que esto es debido a varias razones. Al menos estas son las que yo he identificado que me han impedido utilizarlo con éxito hasta ahora:


  • No tener claro su fundamento y su funcionamiento, para saber qué esperar del sistema y cómo utilizarlo
  • Es muy genérico. No está diseñado para un tipo de uso específico, sino que hay que vincularlo a las entidades que van a intervenir en el proceso de autorización y mantenerlo sincronizado
  • Se estructura en torno a un árbol jerárquico que impone algunas limitaciones, como que un mismo usuario no puede estar a la vez en varios grupos
  • Un problema que no resulta fácil de resolver es cómo determinar los recursos a los que tiene acceso un usuario en un momento dado. Me explico. Si diseñamos un sistema basado en ACL que nos permita saber si un usuario puede ejecutar, o no, una acción, nos queda resolver el problema de a qué objetos puede acceder dentro de ella.

jueves, 13 de mayo de 2010

Forzar la actualización de un registro de un Model

Mi vida como desarrollador es un poco caótica, ya que no me dedico a tiempo completo y últimamente otras demandas me quitan montones de tiempo y toneladas de concentración. Por eso, a pesar de llevar trabajando con CakePHP desde hace ya un par de años, tengo dudas en conceptos básicos o tengo que empezar desde cero con algunos proyectos ya que "pierdo el hilo".

Un ejemplo de estos conceptos básicos que tenía dudosos es lo que pasa cuando creas registros en un modelo y cuando manipulas sus id.

Cuando haces un Model->save() sin indicar un id para el modelo, se crea un nuevo registro. Por tanto, para actualizar un registro existente debes indicar un id.

Esto lo puedes hacer tanto en la propiedad Model->id, como en el array de datos que pases a create o a save. Por ejemplo:


array('Model' => array('id' => 12));


Además, se siguen unas cuantas reglas más:

* Si Model->id tiene un valor y en el array de datos se pasa un valor nuevo, prevalece éste último.
* Si el id que has pasado no existe en la base de datos, se crea un registro nuevo con este id

Saber esto es interesante cuando nos interesa controlar la creación de id's mediante un sistema propio.

Por cierto, ¿qué pasa si nuestro id es de tipo integer pero no tiene auto_increment y no lo especificamos en el Model? Resulta que CakePHP es capaz de emular el auto_increment.