Browsed by
Month: September 2009

[Tutorial] Autenticação com Authlogic + autorização com Acl9

[Tutorial] Autenticação com Authlogic + autorização com Acl9

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/