#188 Declarative Authorization
- Download:
- source codeProject Files in Zip (106 KB)
- mp4Full Size H.264 Video (26.3 MB)
- m4vSmaller H.264 Video (17.3 MB)
- webmFull Size VP8 Video (45.3 MB)
- ogvFull Size Theora Video (38.4 MB)
Éste episodio cubrirá autorización, más especificamente autorización basada en roles. A través de esto, se le puede asignar un rol a un usuario, el cuál le proporciona ciertos permisos dentro de la aplicación. La autorización es un tema difícil de cubrir, más al exisitir tantos plugins y gemas diferentes. Otro problema es que la mejor solución de autorización depende del tipo de aplicación que estás desarrollando.
En este episodio nos enfocaremos en declarative authorization que es una de las soluciones más avanzadas. Al principio puede aparentar ser bastante complicada, pero con poco esfuerzo descubirás que proporciona una poderosa funcionalidad y puede manejar una amplia gama de necesidades de autorización.
Agregando Autorización a Nuestra Aplicación
En este episodio estaremos trabajando en una aplicación de blogging. La página principal de la aplicación muestra una lista de artículos y cada artículo puede tener un número de comentarios que son mostrados en la página de cada uno.
En su estado actual cualquier usuario que visita el sitio puede editar o eliminar cualquier artículo o comentario, que es algo que obviamente no queremos, por lo que modificaremos la aplicación para dar a cada usuario un diferente nivel de funcionalidad dependiendo de su rol. Ya tenemos configurada la autenticación en nuestro sitio y hay enlaces en el cabezal de cada página que permiten a los usuarios loguearse o registrarse. Para esto utilizamos Authlogic, que fue cubierto en el episodio 160 [ver, leer].
Para agregar roles vamos a crear un modelo llamado Role
el cual tendrá un atributo llamado name
. Los modelos Role
y User
estarán vinculados por una relación has_many :through
a través de un modelo de join llamado Assignment
.
Para asignar roles a un usuario modificaremos el formulario de registro para que el usuario pueda seleccionar sus roles. Esto se realiza con una serie de checkboxes usando una técnica descrita en el episodio 17 [ver, leer]. Obviamente en una aplicación del mundo real no permitiríamos al usuario seleccionar sus propios roles, pero para mantener este ejemplo simple lo haremos.
Nuestros tres roles tendrán diferentes permisos: a los miembros del rol admin se les permite crear, editar y eliminar cualquier elemento dentro de la aplicación; a los moderadores se les permite editar los comentarios de cualquier usuario, pero no artículos; los autores podrán crear artículos y editar cualquier artículo que hayan creado, finalmente los usuarios sin un rol asignado podrán crear y editar sus comentarios.
Primeros Pasos Con Declarative Authorization
Ahora que sabemos lo que queremos hacer, comenzemos con los primeros pasos. Declarative authorization está disponible como una gema de Gemcutter por lo que el primer paso que debemos tomar es obtenerla y agregar una referencia en el archivo /config/environment.rb
de nuestra aplicación.
config.gem "declarative_authorization", :source => "http://gemcutter.org"
Luego, para asegurarnos que la gema está instalada corremos
sudo rake gems:install
Definiremos los permisos para cada rol en un nuevo archivo llamado authorization_rules.rb
en el directorio /config
. Declarative Authorization provee su propio DSL para definir roles y permisos y así es como lo usaremos para definir los permisos para el usuario admin.
authorization do role :admin do has_permission_on [:articles, :comments], :to => [:index, :show, :new, :create, :edit, :update, :destroy] end end
Los roles son definidos dentro de un bloque authorization
. Cada rol tiene un nombre y también toma un bloque en el cuál son seteados los permisos. Usamos el método has_permission_on
para definir el permiso el cuál toma dos parámetros. El primero es una lista de los modelos al que el rol puede acceder y le sigue el parámetro :to
que tiene una lista de acciones permitidas sobre los modelos.
Definiremos los otros roles luego y probaremos lo que hemos hecho hasta ahora. Antes de avanzar sin embargo, necesitamos hacer un cambio más debido a que declarative authorization no conoce como nuestros roles son definidos. Necesitamos hacer un cambio a nuestro modelo User
, agregando el método role_symbols
que retorna la lista de roles que tiene el usuario como un array de símbolos. Si, por ejemplo, nuestro modelo Usuario
tenía un atributo admin
podemos escribir el método de este modo para que el usuario pertenezca al role :admin
.
def role_symbols [:admin] if admin? end
Nuestra aplicación es diferente debido a que tiene una relacion many-to-many entre User
y Role
y los roles de un usuario son definidos cuando se registra, a través de los checkboxes que vimos anteriormente. Por lo que en role_symbols
convertiremos los roles del usuario actual en un array de símbolos de la siguiente forma:
def role_symbols roles.map do |role| role.name.underscore.to_sym end end
Declarative authorization podrá ahora mapear los roles del usuario a los roles que estamos definiendo en el archivo authorization_rules
.
Hay un par de pasos que aún debemos tomar. Uno es setear un filtro before_filter
en nuestro ApplicationController
para decirle a declarative authorization cuál es el usuario actual.
class ApplicationController < ActionController::Base helper :all protect_from_forgery before_filter { |c| Authorization.current_user = c.current_user } end
Finalmente debemos ir dentro de cada controlador y agregar un filtro before_filter
para restringir el acceso. Hacemos esto llamando a un método llamado filter_resource_access
que provee declarative authorization. Este método agrega before filters a los controladores de tipo RESTful y además realiza otra tarea: cuando filter_resource_access
es llamado carga el modelo de recurso para que pueda chequear el acceso a él. Esto significa que en cada una de las acciones show
, new
, create
, edit
, update
y delete
podemos quitar la línea que carga el modelo. Podemos hacer esto en ArticlesController
y CommentsController
en nuestra aplicación. Si cualquiera de tus controladores no sigue la convención RESTful puedes consultar la documentación por los detalles de como manejarlo.
Probando Lo Que Hemos Hecho
Ha tomado un poco de trabajo llegar a este punto pero finalmente estamos en la etapa donde podemos probar que hemos hecho hasta ahora. Si nos logueamos como un usuario con rol admin podemos ver que los artículos y los enlaces de editar o eliminar artículo y comentarios están visibles. Hasta ahora todo esta bien, pero cuando hacemos log out vemos algo inesperado:
Esto pasa porque la aplicación redirige a la página principal, la acción index de los artículos, cuando hacemos log out y como invitado no tenemos permisos para ver esta acción. Para arreglar esto necesitamos agregar el permiso para el invitado en el archivo authorization_rules
. El rol invitado es un rol especial reservado para usuarios que no se han registrado, logueado o no tiene un rol.
authorization do role :admin do has_permission_on [:articles, :comments], :to => [:index, :show, :new, :create, :edit, :update, :destroy] end role :guest do has_permission_on :articles, :to => [:index, :show] has_permission_on :comments, :to => [:new, create] end end
En el rol de invitado le damos permiso para acceder a las acciones index y show de artículos y para las acciones new y create de comentarios. Con estos cambios en su sitio recargamos la página y podemos ver la lista de artículos nuevamente.
Ocultando Enlaces a Acciones Prohibidas
A pesar que los usuarios invitados no pueden editar ni eliminar artículos ni comentarios, los enlaces a estas acciones aún se muestran. Nuestro siguiente paso es quitar estos enlaces para los usuarios que no tengan permiso para ejecutar las acciones que representan. Debemos modificar el código de la vista para hacer esto utilizando el método permitted_to?
que brinda declarative authorization.
En el código de la vista para la acción show de ArticleController
utilizaremos permitted_to?
para ocultar los enlaces a editar o eliminar un artículo a menos que el usuario actual tenga los permisos apropiados.
<p> <% if permitted_to? :edit, @article %> <%= link_to "Edit", edit_article_path(@article) %> | <% end %> <% if permitted_to? :destroy, @article %> <%= link_to "Destroy", @article, :method => :delete, :confirm => "Are you sure?" %> | <% end %> <%= link_to "Back to Articles", articles_path %> </p>
El método permitted_to?
toma dos parámetros: el nombre de una acción y un modelo. Más abajo en el mismo archivo haremos una modificación similar de modo que los enlaces no se muestren a menos que el usuario pueda editar o eliminar un comentario.
<p> <% if permitted_to? :edit, comment %> <%= link_to "Edit", edit_comment_path(comment) %> | <% end %> <% if permitted_to? :destroy, comment %> <%= link_to "Destroy", comment, :method => :delete, :confirm => "Are you sure?" %> <% end %> </p>
También necesitamos realizar un cambio similar en la vista index para ocultar el enlace a nuevo artículo.
<% if permitted_to? :create, Article.new %> <p><%= link_to "New Article", new_article_path %></p> <% end %>
Nota que no necesitamos una instancia de artículo en la acción index, utilizamos Article.new
en su lugar.
Cuando recargamos la página ahora los enlaces de editar y eliminar estarán ocultos a menos que el usuario tenga los permisos apropiados.
Agregando Más Roles y Reglas
Ahora que tenemos todo funcionando, todo lo que necesitamos hacer para alterar el acceso a diferentes partes del sitio es realizar cambios al archivo authorization_rules
. El DSL que provee nos permite crear reglas bastante avanzadas. Por ejemplo si queremos permitir a usuarios logueados que no tienen rol asignado poder editar sus propios comentarios, podemos agregar la siguiente regla al rol :guest
.
role :guest do has_permission_on :articles, :to => [:index, :show] has_permission_on :comments, :to => [:new, :create] has_permission_on :comments, :to => [:edit, :update] do if_attribute :user => is { user } end end
Declarative authorization provee un método if_attribute
que puede ser utilizado para agregar condiciones a un permiso. En la nueva regla, el permiso es dado sólo si el atributo :user
, el usuario al que pertenece el comentario, es el usuario actualmente logueado.
Si ahora nos logueamos como un usuario sin roles y agregamos un comentario a un artículo veremos que tenemos el enlace edit en nuestro comentario pero no en los realizados por otros.
Todo lo que debimos hacer para habilitar ésto fue realizar un cambio en el archivo authorization_rules
lo que demuestra cuán fácil es agregar nuevas reglas.
Agregando Permisos Para Otros Roles
Hemos definido tres roles en nuestra aplicación pero por ahora solo definimos permisos para uno de ellos (asi como para el rol invitado). Ahora agregaremos permisos para esos roles.
role :moderator do includes :guest has_permission_on :comments, :to => [:edit,:update] end role :author do includes :guest has_permission_on :articles, :to => [:new, :create] has_permission_on :articles, :to => [:edit, :update] do if_attribute :user => is { user } end end
Nota que hemos utilizado un metodo includes
aquí para darle a un rol el permiso de otro. Cualquier usuario en el rol moderador puede hacer todo lo que hacer un usuario invitado y también editar y actualizar los comentarios de cualquiera. Similarmente los autores pueden hacer todo lo que hace un invitado pero también pueden crear artículos y actualizar cualquier artículo que hayan escrito.
Un Mejor Mensaje de Error
Para finalizar este episodio haremos un último cambio en nuestra aplicación. Si alguien viendo nuestro sitio intenta visitar una acción a la cual no tienen permiso verá una página de error básica con un mensaje diciendo que no están autorizados a hacer eso.
Podemos mejorar esto modificando nuestro ApplicationController
. Si agregamos un método llamado permission_denied
será ejecutado cuando se intenta llamar a una acción no autorizada. Por lo que en lugar de mostrar un error básico haremos que nuestra aplicación nos rediriga a la página principal con un mensaje flash.
def permission_denied flash[:error] = "Sorry, you not allowed to access that page." redirect_to root_url end
Ahora cuando intentamos acceder a una acción que no estamos autorizados seremos redireccionados a la página principal y veremos un mejor mensaje de error.
Eso es todo por este episodio. Declarative authorization provee una solución pura y poderosa para la autorización en aplicaciones Rails y sólo hemos rasgado la superficie en cuanto lo que podemos hacer. Las páginas de documentación proveen mucho más detalles y bien vale la pena leer si quieres profundizar un poco.