Introdução
Autorização é diferente de autenticação e segundo Oleg Dashevskii (criador do Acl9):
Ambas palavras iniciam com “aut”, mas tem significados diferentes! Autenticação é basicamente um mapeamento de credenciais (login e senha) ou OpenID para especificar a conta do usuário no sistema. E autorização é uma permissão de um usuário autenticado para executar alguma ação específica em algum lugar do sistema.
Hoje vou escrever dois tutoriais em um mesmo post, mas eles podem ser usados separadamente. O primeiro é sobre como usar o Authlogic pra fazer autenticação e o segundo é sobre como usar o Acl9 para gerenciar autorizações.
Pré-requisitos
Utilizei Ruby v1.8.6 com as seguintes gems: Rails v2.3.3, Authlogic v2.1.1, Acl9 v0.10.0 e Nifty-generators 0.3.0.
Instalação das gems
[sudo] gem sources -a http://gems.github.com
[sudo] gem install authlogic
[sudo] gem install be9-acl9
[sudo] gem install nifty-generators
Autenticação com AuthLogic
Esta parte do tutorial é praticamente a forma escrita do episódio #160 do RailsCasts.
Atenção: Nós iremos usar o nifty-generators pra facilitar, mas isso não é mandatório para poder utilizar authlogic ou acl9.
Preprarando um simples conteúdo estático
Crie um novo projeto rails:
rails authlogic_acl9
Antes de mais nada, gere um nifty_layout:
script/generate nifty_layout
Inclua no arquivo config/environment.rb a seguinte linha:
config.gem "authlogic"
Agora crie um controller para algumas páginas estáticas:
script/generate controller static_content index
Na página index.html.erb criada, eu escrevi:
<h1>Página inicial</h1>
Pra que esta seja a página inicial da app, apague o arquivo public/index.html e adicione a seguinte linha no config/routes.rb:
map.root :controller => "static_content", :action => "index"
User
Agora que já foi criado uma página estática simples, crie um scaffold para o User:
script/generate nifty_scaffold user username:string email:string password:string new edit
Para quem não conhece o nifty_scaffold, os últimos parametros que foram passados são os controllers (neste caso ‘new’ e ‘edit’ – com os quais ganhamos automáticamente o ‘create’ e ‘update’, por motivos óbvios).
Altere o migration da tabela Users para:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :username
t.string :email
t.string :crypted_password
t.string :password_salt
t.string :persistence_token
t.timestamps
end
end
def self.down
drop_table :users
end
end
Exitem outras colunas interessantes que podem ser usadas, como por exemplo login_count (integer) e current_login_ip (string). Veja mais colunas neste link.
Gere a tabela:
rake db:migrate
Inclua a seguinte linha no seu models/user.rb:
class User < ActiveRecord::Base
acts_as_authentic
end
Altere o users/_form.html.erb, incluindo um campo de confirmação de senha:
<% form_for @user do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :username %><br />
<%= f.text_field :username %>
</p>
<p>
<%= f.label :email %><br />
<%= f.text_field :email %>
</p>
<p>
<%= f.label :password %><br />
<%= f.password_field :password %>
</p>
<p>
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %>
</p>
<p><%= f.submit "Enviar" %></p>
<% end %>
O interessante é que o AuthLogic já reconhece esse campo e não cadastra o usuário se as senhas não coencidirem, exibindo um erro de validação.
Agora altere o users_controller.rb:
class UsersController < ApplicationController
def new
@user = User.new
end
def create
@user = User.new(params[:user])
if @user.save
flash[:notice] = "Usuário cadastrado com sucesso."
redirect_to root_url
else
render :action => 'new'
end
end
def edit
@user = current_user
end
def update
@user = current_user
if @user.update_attributes(params[:user])
flash[:notice] = "Dados do usuário alterados com sucesso."
redirect_to root_url
else
render :action => 'edit'
end
end
end
Session
Vamos agora começar a gerenciar as sessões:
script/generate session user_session
script/generate nifty_scaffold user_session --skip-model username:string password:string new destroy
Na primeira linha, você vê um script (o qual faz parte do AuthLogic) que gera a session.
E na segunda, usamos novamente o nifty_scaffold, só que agora ignorando o model (com o parametro –skip-model), uma vez que ele já foi gerado pelo primeiro script (generate session).
Altere o user_sessions_controller.rb:
class UserSessionsController < ApplicationController
def new
@user_session = UserSession.new
end
def create
@user_session = UserSession.new(params[:user_session])
if @user_session.save
flash[:notice] = "Usuário logado com sucesso."
redirect_to root_url
else
render :action => 'new'
end
end
def destroy
@user_session = UserSession.find
@user_session.destroy
flash[:notice] = "Sessão finalizada com sucesso."
redirect_to root_url
end
end
E user_sessions/new.html.erb será a página de login:
<% title "Login" %>
<% form_for @user_session do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :username %><br />
<%= f.text_field :username %>
</p>
<p>
<%= f.label :password %><br />
<%= f.password_field :password %>
</p>
<p><%= f.submit "Enviar" %></p>
<% end %>
Adicione no routes.rb as rotas para login e logout:
map.login "login", :controller => "user_sessions", :action => "new"
map.logout "logout", :controller => "user_sessions", :action => "destroy"
No layouts application.html.erb, inclua um menu que irá gerenciar o usuário e as sessões:
<div id="user_nav">
<% if current_user %>
<%= link_to "Editar", edit_user_path(:current) %> |
<%= link_to "Logout", logout_path %> |
<span>Bem vindo <strong><%= current_user.username %></strong>!</span>
<% else %>
<%= link_to "Registrar", new_user_path %> |
<%= link_to "Login", login_path %>
<% end %>
</div>
Pra finalizar, inclua dois métodos ao application_controller.rb, os quais retornam o usuário autenticado:
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery # See ActionController::RequestForgeryProtection for details
#Authlogic
filter_parameter_logging :password
helper_method :current_user
private
def current_user_session
return @current_user_session if defined?(@current_user_session)
@current_user_session = UserSession.find
end
def current_user
return @current_user if defined?(@current_user)
@current_user = current_user_session && current_user_session.record
end
end
Pronto! O AuthLogic já está gerenciando suas sessões
Uma opção bem interessante é traduzir as mensagens, campos e modelos do Authlogic para pt-BR. O Patrick Espake explica como fazer isso, neste post.
Autorização com Acl9
Nesta segunda parte do tutorial, vamos gerenciar as autorizações atravéz do Acl9.
Bem, como eu disse no início do post, são dois tutoriais independentes, mas irei usar a aplicação que criamos no primeiro tutorial pra adiantar as coisas.
Inclua mais uma linha no seu config/environment.rb:
config.gem "be9-acl9", :source => "http://gems.github.com", :lib => "acl9"
Irei usar algumas actions sem sentido, apenas para efeito didático. Vamos então recriar o controller static_content:
script/generate controller static_content index index2 index3 index4 denied
Adicione as novas actions no routes.rb:
map.root :controller => "static_content", :action => "index"
map.resources :static_content, :collection => {:index2 => :get, :index3 => :get, :index4 => :get, :denied => :get}
No final do layout application.html.erb, inserimos um rodapé com alguns links, pra testar se as permissões de fato funcionam corretamente:
<div id="footer">
<%= link_to 'index', :controller => 'static_content', :action => 'index' %> |
<%= link_to 'index2', :controller => 'static_content', :action => 'index2' %> |
<%= link_to 'index3', :controller => 'static_content', :action => 'index3' %> |
<%= link_to 'index4', :controller => 'static_content', :action => 'index4' %>
</div>
Vamos trabalhar o rescue ‘AccessDenied’ no application_controller.rb:
rescue_from 'Acl9::AccessDenied', :with => :access_denied
def access_denied
if current_user
render :template => 'static_content/denied'
else
flash[:notice] = 'Acesso negado. Você precisa estar logado.'
redirect_to login_path
end
end
Este método faz o seguinte: se existir uma sessão de usuário e ocorrer esta excessão, significa que este usuário específico não tem permissão pra acessar tal recurso, então é renderizado o ‘static_content/denied’. Caso ele não esteja logado, aparece uma mensagem flash dizendo que ele precisa se logar e redireciona-o para tela de login.
Continuando, nós precisaremos de um model Role:
script/generate model Role name:string authorizable_type:string authorizable_id:integer
Altere a migration Roles para:
class CreateRoles < ActiveRecord::Migration
def self.up
create_table "roles", :force => true do |t|
t.string "name", :limit => 40
t.string "authorizable_type", :limit => 40
t.integer "authorizable_id"
t.timestamps
end
end
def self.down
drop_table :roles
end
end
E o models/role.rb para:
class Role < ActiveRecord::Base
acts_as_authorization_role
end
Assim, o Acl9 entende que está é a classe das regras de autorização.
Embora não seja necessário alterar nada na tabela Users, é necessário uma tabela intermediária dele com as regras, então crie a migration roles_users:
script/generate migration roles_users
E edite esta nova migration:
class RolesUsers < ActiveRecord::Migration
def self.up
create_table "roles_users", :id => false, :force => true do |t|
t.integer "user_id"
t.integer "role_id"
t.datetime "created_at"
t.datetime "updated_at"
end
end
def self.down
end
end
Não esqueça de gerar as tabelas:
rake db:migrate
Assim como fizemos no model Role, é necessário dizer ao Acl9 quem são os usuários, inserindo ‘acts_as_authorization_subject’ ao models/user.rb:
class User < ActiveRecord::Base
acts_as_authentic
acts_as_authorization_subject
end
Brincando com as autorizações
Bem, está tudo pronto! Aqui começa a diversão
No controller static_content_controller.rb, adicione no início da classe:
class StaticContentController < ApplicationController
access_control do
allow all
end
#...
Neste ponto, nada deve ter mudado (a não ser que algo tenha dado errado na instalação do Acl9), simplesmente porque estamos dando permissão de todas as actions para todos os usuários.
Vamos restringir algumas coisas:
access_control do
allow all, :to => [:index]
allow anonymous, :to => [:index2]
allow logged_in, :to => [:index2, :index3]
end
Na linha allow all, :to => [:index], estamos dizendo que todos podem acessar a index. Na linha allow anonymous, :to => [:index2], somente usuários não logados podem acessar a index2, ou seja, se você estiver logado, talvez você não acesse a index2, exceto se existir uma regra explicita dizendo isso. Neste exemplo isso acontece, como você verica na última linha deste bloco: allow logged_in, :to => [:index2, :index3], claramente é notável que os usuários logados podem acessar a index2 e index3. Crie um usuário e você vai verificar que só quando você estiver logado você acessa a index3.
Ok, mas o que realmente queremos é criar regras diferentes para usuários diferentes e não apenas usuários logados e não-logados. Para isso:
access_control do
allow all, :to => [:index]
allow anonymous, :to => [:index2]
allow logged_in, :to => [:index2, :index3]
allow :admin, :to => [:index4]
end
Aqui fica interessante! Só o usuário que tiver a regra ‘admin’ vai acessar. E para fazer isso, basta associar esta regra à um usuário:
User.find(2).has_role! :admin
E se por algum motivo você precisar verificar se o usuário tem determina regra ou não, basta usar:
User.find(2).has_role? :admin
#Retorna um boolean.
Bom, é isso.
Se tiverem dúvidas, postem nos comentários ou acessem o grupo oficial do Acl9.
— Atualizado —
Coloquei o projeto desenvolvido neste tutorial no GitHub:
http://github.com/lucascaton/authlogic_acl9_example/