Autorização com pattern e PORO, pundit no Rails

Autorização com pattern e PORO, pundit no Rails
Photo by Jonathon Young / Unsplash

Neste post, vou explicar como implementar autorização em uma aplicação Ruby on Rails usando a gem Pundit. Abordaremos desde a instalação e configuração até a criação de testes para controllers, políticas e views. Também discutiremos a diferença entre autenticação e autorização e o padrão de design que o Pundit segue.

O que é Pundit?

Pundit é uma gem popular para gerenciar autorizações em aplicações Rails. Ela fornece um conjunto de helpers para definir políticas de acesso, facilitando a gestão de permissões de forma clara e modular.

Autenticação vs. Autorização

  • Autenticação é o processo de verificar a identidade de um usuário (ex: login).
  • Autorização é o processo de determinar se um usuário autenticado tem permissão para realizar uma ação específica.

Instalando o Pundit

  1. Adicione a gem ao seu Gemfile:

    gem 'pundit'
    
  2. Execute o bundle para instalar a gem:

    bundle install
    
  3. Gere a instalação do Pundit:

    rails g pundit:install
    

Configurando Pundit

  1. Inclua o Pundit nos seus controllers:

    # app/controllers/application_controller.rb
    include Pundit
    
  2. Crie uma política:

    rails g pundit:policy post
    

Criando Políticas

  1. Defina regras na classe de política:
    # app/policies/post_policy.rb
    class PostPolicy
      attr_reader :user, :post
    
      def initialize(user, post)
        @user = user
        @post = post
      end
    
      def update?
        user.admin? || post.user == user
      end
    end
    

Usando Pundit nos Controllers

  1. Autorize ações no controller:
    # app/controllers/posts_controller.rb
    def update
      @post = Post.find(params[:id])
      authorize @post
      # Resto do código de atualização
    end
    

Testando com RSpec

Testando Políticas

  1. Crie um teste para a política:
    # spec/policies/post_policy_spec.rb
    describe PostPolicy do
      subject { described_class }
    
      let(:user) { User.create }
      let(:post) { Post.create(user: user) }
    
      permissions :update? do
        it "permite a atualização se o usuário é o autor do post" do
          expect(subject).to permit(user, post)
        end
    
        it "não permite a atualização se o usuário não é o autor do post" do
          other_user = User.create
          expect(subject).not_to permit(other_user, post)
        end
      end
    end
    

Testando Controllers

  1. Teste a autorização no controller:
    # spec/controllers/posts_controller_spec.rb
    describe PostsController do
      describe "PUT #update" do
        let(:user) { User.create }
        let(:post) { Post.create(user: user) }
    
        context "quando o usuário está autorizado" do
          before { sign_in user }
    
          it "atualiza o post" do
            put :update, params: { id: post.id, post: { title: "Novo Título" } }
            expect(response).to be_successful
          end
        end
    
        context "quando o usuário não está autorizado" do
          it "não atualiza o post e redireciona" do
            put :update, params: { id: post.id, post: { title: "Novo Título" } }
            expect(response).to redirect_to(root_path)
          end
        end
      end
    end
    

Testando Views

  1. Teste a visibilidade dos elementos com base na política:

    # spec/views/posts/show.html.erb_spec.rb
    it "mostra o link de edição para usuários autorizados" do
      assign(:post, post)
      enable_pundit(view, user)
      render
      expect(rendered).to include('Editar')
    end
    
  2. Use os helpers do Pundit nas views:

    <% if policy(@post).update? %>
      <%= link_to 'Editar', edit_post_path(@post) %>
    <% end %>
    

Rails API

Passo 1: Adicionar a Gem Pundit

  1. Adicione a gem Pundit ao seu Gemfile:

    gem 'pundit'
    
  2. Execute o comando para instalar a gem:

    bundle install
    

Passo 2: Configurar o Pundit

  1. Gere o instalador do Pundit:

    rails g pundit:install
    
  2. Isso criará um arquivo de política de aplicação em app/policies/application_policy.rb.

  3. Inclua o Pundit no ApplicationController:

    # app/controllers/application_controller.rb
    include Pundit
    

Passo 3: Criar Políticas

  1. Crie uma política para um modelo, por exemplo, Post:

    rails g pundit:policy post
    
  2. Defina as regras na política:

    # app/policies/post_policy.rb
    class PostPolicy < ApplicationPolicy
      def update?
        user.admin? || record.user == user
      end
    end
    

Passo 4: Usar Pundit nos Controllers

  1. Autorize as ações no controller da API:
    # app/controllers/api/v1/posts_controller.rb
    class Api::V1::PostsController < ApplicationController
      def update
        post = Post.find(params[:id])
        authorize post
        # Código de atualização
      end
    end
    

Passo 5: Testar com RSpec

Testando Políticas

  1. Crie um teste para a política:
    # spec/policies/post_policy_spec.rb
    RSpec.describe PostPolicy, type: :policy do
      let(:user) { create(:user) }
      let(:post) { create(:post, user: user) }
    
      subject { described_class }
    
      permissions :update? do
        it "permite a atualização para o autor do post" do
          expect(subject).to permit(user, post)
        end
    
        it "não permite a atualização para outros usuários" do
          other_user = create(:user)
          expect(subject).not_to permit(other_user, post)
        end
      end
    end
    

Testando Controllers

  1. Teste a autorização no controller da API:
    # spec/controllers/api/v1/posts_controller_spec.rb
    RSpec.describe Api::V1::PostsController, type: :controller do
      describe 'PUT #update' do
        let(:user) { create(:user) }
        let(:post) { create(:post, user: user) }
    
        before do
          request.headers.merge!(user.create_new_auth_token)
        end
    
        it 'atualiza o post para usuários autorizados' do
          put :update, params: { id: post.id, post: { title: 'Novo Título' } }
          expect(response).to have_http_status(:success)
        end
    
        it 'não permite a atualização para usuários não autorizados' do
          other_user = create(:user)
          request.headers.merge!(other_user.create_new_auth_token)
          put :update, params: { id: post.id, post: { title: 'Novo Título' } }
          expect(response).to have_http_status(:forbidden)
        end
      end
    end
    

Com o Pundit, você pode gerenciar autorizações de forma eficiente e modular em sua aplicação Rails. Lembre-se de que a autorização é um aspecto crítico da segurança da aplicação e deve ser tratada com cuidado.

Ao escolher uma gem para gerenciar autorizações em uma aplicação Ruby on Rails, as duas opções mais populares são Pundit e CanCanCan. Cada uma tem suas características e melhor adequação dependendo do projeto. Vamos explorar as diferenças entre elas e quando você pode preferir uma sobre a outra.

Pundit

Características:

  • Pundit utiliza uma abordagem minimalista e orientada a objetos.
  • As autorizações são definidas em classes de política separadas, uma para cada modelo.
  • Pundit não impõe uma estrutura específica, dando mais liberdade ao desenvolvedor.
  • É mais adequado para aplicações onde as regras de autorização são complexas e variadas.

Quando usar:

  • Se você prefere uma abordagem mais explícita e orientada a objetos.
  • Quando as regras de autorização são complexas e específicas para cada recurso.
  • Se você deseja manter as regras de autorização desacopladas do modelo.

CanCanCan

Características:

  • CanCanCan é uma continuação do CanCan, originalmente desenvolvido por Ryan Bates.
  • Centraliza as regras de autorização em uma única classe Ability.
  • Usa uma DSL (Domain Specific Language) para definir habilidades, o que pode tornar o código mais legível.
  • É mais adequado para aplicações com regras de autorização mais simples e diretas.

Quando usar:

  • Se você prefere ter todas as regras de autorização em um único local.
  • Quando as regras de autorização são relativamente simples e não variam muito entre os modelos.
  • Se você deseja uma configuração rápida e fácil com menos personalização.

Outras Gems

Outras gems, como Rolify e Devise, também são frequentemente usadas em conjunto com Pundit e CanCanCan, mas para propósitos ligeiramente diferentes. Rolify é usada para gerenciar roles (papéis) de usuários, enquanto Devise é mais focada na autenticação.

  • Pundit é ideal para aplicações que exigem regras de autorização complexas e personalizadas, com uma abordagem mais orientada a objetos.
  • CanCanCan é mais adequado para aplicações que podem centralizar suas regras de autorização e que têm requisitos de autorização mais simples e diretos.

Cada projeto tem suas necessidades específicas, então a escolha entre Pundit e CanCanCan (ou outra gem) deve ser baseada nas características do projeto e nas preferências da equipe de desenvolvimento.

Referências

  1. Pundit GitHub
  2. CanCanCan GitHub
  3. Rolify GitHub
  4. Devise GitHub

Read more