July 13, 2011
Construindo um sistema de autorização de usuário com herança

Que tal construir um sistema simples e funcional de autorização de usuários de forma rápida e bem elegante?

Lembrando: Quando falamos em autorização, estamos nos referindo as permissões dos usuários, o que cada tipo de usuário pode fazer ou não.

Acredito que a maioria das pessoas quando estudou orientação a objetos, teve como exemplo uma classe Cachorro que herda de uma classe Animal, certo? Vamos relembrar exemplo?

Relembrando

Classe Animal

class Animal
  def comer
    ...
  end
end

Aqui temos uma classe Animal que possui um método comer.

Classe Cachorro

class Cachorro < Animal
  def latir
    ...
  end
end

Já aqui, temos uma classe Cachorro que herda tudo de Animal, inclusive o método comer. Sendo que a classe Cachorro possui um método único que não esta disponível na classe Animal, o método o latir, pois não queremos que todos que herdarem de Animal tenham o método latir, certo? Imaginem um gato latindo… hmm seria meio estranho isso… enfim, continuando…

Classe Gato

class Gato < Animal
  def miar
    ...
  end
end

Da mesma forma que foi implementado a classe Cachorro, foi implementado a classe Gato, porém com seu método exclusivo miar.

Tá, e o que tudo isso tem haver com o sistema de autorização de usuários? Estamos querendo que pessoas utilizem nossas aplicações, não animais! Imagine chegar em casa e ver seu cachorro twittando?! Se bem que nos dias de hoje não duvido mais de nada… ok, vamos em frente… Se pararmos para analisar a estrutura acima, veremos que é exatamente isso que precisamos, não de cachorros twittando, mas sim em termos conceituais, vejam:

Cenário

Vamos imaginar um blog, não! Chega de exemplo de blog… Vamos imaginar uma rede social, o Facebook por exemplo, onde:

  1. Teremos usuários
  2. Apenas usuários com o tipo Manager podem ter grupos
  3. Um grupo terá comentários, porém somente quem estiver participando do grupo poderá postar um comentário

Vamos lá?

Observação1: Irei apenas tratar das permissões em si, não irei mencionar relacionamentos, validações e etc…

Observação2: Estou utilizando STI para tratar tipos de usuários.

Primeiro vamos definir as permissões no modelo de grupos:

group.rb

class Group < ActiveRecord::Base
  ... # associations, validations, scopes

  def is_owner?(_user)
    self.user == _user
  end

  def is_participant?(_user)
    self.participants.include?(_user)
  end

  # Permissions
  # Who can create a gruop?
  def self.can_create?(_user)
    _user.can_create_group?
  end

  # Who can edit this group?
  def can_edit?(_user)
    self.is_owner?(_user)
  end

  # Who can destroy this group?
  def can_destroy?(_user)
    self.is_owner?(_user)
  end

  # Who can create comments?
  def can_create_comment?(_user)
    self.is_owner?(_user) || self.is_participant?(_user)
  end

  # Who can destroy this comment?
  def can_destroy_comment?(_user, _comment)
    self.is_owner?(_user) || _comment.user == _user
  end
end

Começamos a definir aqui as permissões do grupo, quem pode criar, quem pode editar e quem pode remover. Cada permissão poderá ser chamada através de uma variável de instância.

user.rb

class User < ActiveRecord::Base
  ... # associations, validations, scopes

  # Permissions
  # Can create a gruop?
  def can_create_group?
    false
  end
end

manager.rb

class Manager < User
  ... # associations, validations, scopes

  # Permissions
  # Can create a gruop?
  def can_create_group?
    true
  end
end

Note que aqui algo interessante acontece, a classe User possui o método can_create_group? retornando false, já a classe Manager retornando true, o que indica exatamente o que foi especificado nos nossos requisitos, onde somente os usuários do tipo Manger poderão criar grupos. Dessa maneira, cada classe que herdar de User automaticamente não poderá criar um grupo! Vamos implementar?

Precisamos tratar para que somente usuários com permissão vejam o botão “Criar novo grupo”.

views/groups/index.html.erb

...
  <%- if Group.can_create?(current_user) -%>
    <%= link_to "New Group", new_group_path %>
  <%- end -%>
...

Assim, onde tiver link para criar um novo grupo, será verificado se o usuário logado(current_user) possui permissão ou não.

Fácil, não? Então acabamos? Não… imagine se o usuário souber a url para criar um novo grupo? ele poderá burlar seu sistema apenas copiando e colando na barra de endereço…

Então quer dizer que eu li esse post inteiro pra você vir e dizer que não funciona bem? Que não tem segurança?

Fique tranquilo! Basta somente mudar as rotas para que fiquem bem difíceis! Brincadeira! Vamos a uma solução decente!

A solução

groups_controller.rb

class GroupsController < ApplicationController
  before_filter :can_create?, :only => [:new, :create] 
  ...
  private
  def can_create?
    redirect_to(groups_path, :notice => "Malandrão!") unless Group.can_create?(current_user)
  end
end

Problema resolvido! Caso o usuário tente forçar a url, passará pelo before_filter no groups_controller.rb, que verificará se o usuário possui permissão para criar um grupo! 

Da mesma forma iremos tratar quem poderá criar/remover um comentário em um grupo, utilizando os métodos can_create_comment? e can_destroy_comment?.

Bem, é isso ai pessoal. Isso foi somente um exemplo de como você pode tratar autorização de usuários com herança, logicamente você terá de criar mais permissões para toda sua aplicação, mas seguindo esse modelo não tem erro!

Abraços!


Blog comments powered by Disqus