Autorização com cancancan no Rails — Verificando permissões
Tags: cancancan, ruby, authorization, rails
Published: 2021-01-31 15:58:43

---
title: Autorização com cancancan no Rails — Verificando permissões
published: true
date: 2020-08-03 01:32:52 UTC
tags: cancancan,ruby,authorization,rubyonrails
canonical_url: https://medium.com/sumone-technical-blog/autoriza%C3%A7%C3%A3o-com-cancancan-no-rails-verificando-permiss%C3%B5es-eda3d9481909
---

### Autorização com cancancan no Rails — Verificando permissões

> O código usado como exemplo para esse post está no github, no link [https://github.com/jplethier/cancancan-examples](https://github.com/jplethier/cancancan-examples)

![](https://cdn-images-1.medium.com/max/1024/1*HYQXfHVWoM00c-X8Tot-lg.jpeg)

No [post anterior sobre o cancancan](https://medium.com/sumone-technical-blog/autoriza%C3%A7%C3%A3o-com-cancancan-no-rails-definindo-permiss%C3%B5es-d508a71fc55), abordei a questão das definições das permissões na classe Ability, os métodos can e cannot, como combinar permissões e a precedência nesses casos, como utilizar um hash como terceiro parâmetro para definir mais condições, e o uso de alias para agrupar ações.

Nesse post, pretendo aprofundar um pouco em como verificar essas permissões nos controllers e nas views do projeto. Para entender melhor, vamos usar a mesma base de cenário do post anterior sobre definição de permissões, considerando a estrutura de Task e User. Abaixo temos a classe Ability definida no post anterior para facilitar o entendimento e lembrança nesse post:

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0561be6a3a53c5e9d491a58706785181/href">https://medium.com/media/0561be6a3a53c5e9d491a58706785181/href</a></iframe>
#### current\_ability

Todo o funcionamento das verificações do cancancan dependem de um método chamado current\_ability, que é um método de controller, também adicionado como helper, e que o cancancan adiciona no projeto automaticamente. O código desse método é o seguinte:

```
def current\_ability
  @current\_ability ||= Ability.new(current\_user)
end
```

Como podemos perceber, esse método pressupões duas coisas:

- Que a classe onde foram definidas as permissões se chama Ability;
- E que existe um helper method no controller que se chame current\_user, que retorne o usuário logado atualmente no sistema.

Por ter essas expectativas, em alguns casos é necessário sobrescrever esse método current\_ability para ter o funcionamento correto. Por exemplo, caso tenha mais de um namespace no projeto e cada um tenha sua própria classe de permissões, como Web::Ability e API::Ability, ou tenham sido criadas classes específicas para cada context, como User::Ability e Task::Ability, será necessário sobrescrever o método para usar a classe certa em cada contexto(usando o API::Ability no API::ApplicationController, por exemplo).

No caso de não ter o método current\_user, podemos tanto sobrescrever o método current\_ability, como definir um alias para outro método. Por exemplo, seguindo o cenário de ter um API::ApplicationController, e imaginando que nesse contexto o método que retorna o usuário que está fazendo o request é chamado current\_api\_user, podemos escolher entre sobrescrever o método com Ability.new(current\_api\_user) ou colocar alias\_method :current\_user, :current\_api\_user no controller da API.

#### Verificando permissões nas views

Um dos usos mais comuns para verificação de permissões acontece nas views dos projetos. Por exemplo, quando precisamos montar um menu e definir quais itens do menu o usuário tem permissão de visualizar e/ou clicar. Ou em uma listagem onde cada item listado possui botões de ações de edição, remoção e visualização de detalhes, e precisamos saber se o usuário tem as permissões necessárias para executar essas ações.

Dado que o método current\_ability existe e está definido corretamente, podemos utilizar outro helper do cancancan para nos ajudar nesses cenários: o método can?. Para exemplificar o uso desse método, vou usar o exemplo de uma listagem de tarefas:

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f402dac0daf271e3c1c12849684513d0/href">https://medium.com/media/f402dac0daf271e3c1c12849684513d0/href</a></iframe>

Como podemos ver no código acima, o uso do método can? é bem simples e intuitivo. Ele recebe a ação e o objeto a serem verificados a permissão, e esses dois parâmetros seguem a mesma ordem dos parâmetros do método can da classe Ability(visto no post anterior), com a ação sendo passada primeiro, e o objeto sendo o segundo parâmetro.

Por trás dos panos, quando utilizamos can? :approve, task, o que acontece é que o cancancan vai "perguntar" para o current\_ability se o usuário logado pode executar a ação de aprovar essa determinada task. Ou seja, no final das contas, o método can? acaba sendo um próprio método da instância definida em Ability.new, ou seja, seria o mesmo que utilizar diretamente na view current\_ability.can? :approve, task, mas a possibilidade de esconder o current\_ability na chamada deixa as views muito mais limpas.

**Verificando permissão sem ter um objeto específico**

Um outro cenário que citei como muito comum ser necessário o uso das verificações de permissões é na construção de um menu de navegação. Vamos ver como ficaria um exemplo desse cenário:

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/34fb6791f869420b0c52e119a9a7c8d7/href">https://medium.com/media/34fb6791f869420b0c52e119a9a7c8d7/href</a></iframe>

Essa parte é um pouco mais confusa, pois ao invés de passarmos um objeto específico para o método can?, passamos uma classe para ele. O caminho para entendermos esse uso do can? é lermos ele como se fosse a seguinte pergunta: "O usuário logado atual tem permissão para ler **alguma** tarefa?".

O ponto chave é na palavra **alguma** , pois quando passamos para o ability uma classe ao invés de um objeto específico, ele vai nos retornar que a permissão existe desde que ela **possivelmente** exista para **pelo menos um** objeto daquela instância. Ok, o alguma está entendido, mas **possivelmente**? Como assim?

Para entender melhor, vamos a um exemplo. Imaginem um cenário onde um usuário comum(não é admin e nem moderador) não criou nenhuma tarefa ainda, ou seja, Task.where(user\_id: user.id) retorna uma lista vazia. Nesse cenário, não existe nenhum registro no banco de dados de uma tarefa que o usuário tenha permissão para ler, porém, ao executar a verificação da permissão, o ability não irá no banco de dados. O que vai ser feito será simplesmente uma verificação de que existe uma permissão onde o usuário pode ler tarefas com uma condição específica, mas não será verificado no banco de dados se existe alguma tarefa nessa condição. Dessa forma o ability retorna que sim, o usuário pode ler alguma tarefa.

Uma forma de resumir melhor essa regra é pensar que a verificação feita é o oposto, ou seja, o que é verificado é se o usuário **não pode** ler **nenhuma tarefa** , e só é retornado falso para o can? caso o ability consiga confirmar com 100% de certeza essa afirmação de **não ter** permissão para **nenhuma**  tarefa.

Para finalizar sobre a verificação de permissões nas views, é importante citar que também é possível utilizar o método cannot?. Como o próprio nome já deixa claro, ele é a negação do método can?, e na prática normalmente não é quase utilizado, sendo recomendado utilizar o unless can? ... sempre que possível.

#### Verificando permissões nos controllers

Quanto estamos escrevendo os controllers do projeto, temos duas preocupações em relação a autorização: a primeira é que precisamos verificar se o usuário pode chamar aquele endpoint, se pode executar aquela action do controller; a outra é que mesmo que ele possa executar a action, precisamos verificar quais objetos podem ser manipulados por esse usuário nessa action.

Seguindo o cenário usado no tópico anterior de listagem das tarefas, na action de index do TasksController, precisamos verificar se o usuário pode ler alguma tarefa, e em seguida precisamos saber **quais** tarefas ele pode ler. Isso pode ser feito com o código abaixo:

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/662ad56be7137ae542375a1e5712d403/href">https://medium.com/media/662ad56be7137ae542375a1e5712d403/href</a></iframe>

Aqui já temos o uso do outro helper adicionado ao controller pelo cancancan, assim como de um scope novo, também adicionado pela gem aos models do projeto.

O authorize! funciona de forma muito similar ao método can? usado nas views, onde ele recebe a ação e o objeto/classe a ser verificado junto com a ação. A diferença para o método can? é que o authorize! não retorna um booleano, mas sim joga a exceção Cancan::AccessDenied e aborta a execução do [método com esse raise](https://github.com/CanCanCommunity/cancancan/wiki/Exception-Handling). Dessa forma, não precisamos colocar ifs em todos as actions, e podemos cuidar dessa exceção de forma centralizada, por exemplo, podemos colocar um rescue no ApplicationController e renderizar uma página genérica de acesso não autorizado sempre que um authorize! de alguma action jogar essa exceção.

Já o scope utilizado para buscar as tarefas no banco de dados, ele recebe o current\_ability, e por default busca todas as instâncias do model onde está sendo chamado que podem ser lidos(a ação :index é utilizada como regra por default). Também é possível passar uma action específica como segundo parâmetro, por exemplo, poderíamos chamar Task.accessible\_by(current\_ability, :approve), e nesse caso ele retornaria todas as tasks que podem ser aprovadas de acordo com as permissões do current\_ability. A query no banco de dados é montada usando as condições passadas como terceiro parâmetro(hash conditions) para o método can que define a permissão para a action requisitada(segundo parâmetro do scope, index por default caso o parâmetro não seja passado).

#### Usando before\_actions para autorizar actions dos controllers

Usar o authorize! em todas as actions dos controllers pode se tornar muito repetitivo e verboso, além de ser muito propício a erros de desenvolvimento que gerariam bugs. Para facilitar essa verificação, o cancancan tem um outro helper, que pode ser adicionado no controller, onde o controller ficaria da seguinte forma:

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0e09570414e505a36e02e8364e13bcbd/href">https://medium.com/media/0e09570414e505a36e02e8364e13bcbd/href</a></iframe>

Esses dois helpers utilizados(load\_resource and authorize\_resource) rodam como before\_actions do controller, e fazem todas as verificações necessárias de permissões para carregar a lista(no caso do index) ou objeto(actions como show e edit, por exemplo) e também executam o authorize! para verificar a permissão de acesso na action do controller. Esses dois métodos podem ser utilizados de forma separada como mostrado acima, inclusive sendo utilizado somente um dos dois de forma isolada, como também podemos simplificar a chamada aos dois com o método load\_and\_authorize\_resource no lugar de chamar os dois separadamente.

Assim como um before\_action comum, esses três métodos aceitam um hash de condições, seja para definir que eles rodem em somente algumas actions, usando only: %i[index update] por exemplo, ou para definir que eles não rodam em algumas actions, except: %i[index update] . Existem outras opções também que podem ser passadas para sobrescrever o método find, o método new, e outras coisas. Para aprofundar em todas as regras e cenários, vale ler a [wiki própria da gem](https://github.com/CanCanCommunity/cancancan/wiki/Authorizing-controller-actions#choosing-actions) sobre autorização nos controllers.

**Fluxo de criação(new e create)**

Em todos os exemplos anteriores, sempre falamos de como o cancancan facilita a busca no banco de dados e a verificação se existe permissão para o usuário atual executar a ação no(s) objeto(s) retornados do banco de dados. Mas e no caso das actions de new e create?

Nessas actions, o load\_resource do cancancan instancia de forma automática o objeto correspondente ao model do controller, seguindo as seguintes regras:

- Na action de new, o objeto é instanciado com as condições definidas no Ability
- Na action de create, o objeto é instanciado com os parametros enviados do formulário

Vamos novamente para o controller de tarefas para olhar exemplos e tentar trazer mais clareza, considerando o usuário logado um usuário normal(nem moderador, nem admin):

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f3eb933ce7242c40fb94d54256325415/href">https://medium.com/media/f3eb933ce7242c40fb94d54256325415/href</a></iframe>

Nesse exemplo, considerando que definimos no ability que o user normal só pode criar tarefas para ele próprio, na action new a parte doload\_resource funciona da mesma forma como se a gente executasse diretamente na action @task = Task.new(user\_id: current\_user.id), pois dado que a condição passada para o método can do ability foi user\_id: user.id, ele criará a task com o id do user passado para o ability(no caso, o current\_user). Dessa forma, ele garante que o objeto criado tem sempre as condições definidas na permissão e sem ter a necessidade de chamar o authorize! na instância recém criada.

Já no create, o cancancan espera que exista um método com o nome task\_params(seguindo a convenção MODELNAME\_params, nesse caso task sendo o nome do model) para instanciar o objeto usando Task.new(task\_params). Em seguida, ele vai chamar o método authorize! para verificar a permissão de criação para o objeto instanciado com os parâmetros enviados, e se por algum motivo o usuário estiver tentando criar uma task com um user\_id diferente do id dele, vai ser jogada a exceção Cancan::AccessDenied.

**Fluxo de atualização ou remoção(show, edit, update e destroy)**

As actions de show, edit, update e destroy funcionam de forma muito similar a action de create explicada acima. A diferença é somente na forma de popular a variável @task(no nosso caso do tasks controller). Ao invés de criar uma nova instância, é executado um find para buscar no banco. Esse find espera que as rotas sigam a convenção do rails com o id na url, e que o método find funcione conforme o esperado ao receber o id(ou seja, não tenha sido sobrescrito para funcionar de outra forma). Após o find, é executado o authorize! para verificar a permissão para a ação e o objeto encontrado.

É possível passar para o load\_and\_authorize\_resource um parâmetro find\_by: :method\_name definindo um método customizado para ser usado para a busca no banco de dados.

Para mais clareza, segue abaixo o resultado final do tasks\_controller com todas as actions de um CRUD criados e usando o load\_and\_authorize\_resource:

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a9f3335821d649d5c8b390b267c1d711/href">https://medium.com/media/a9f3335821d649d5c8b390b267c1d711/href</a></iframe>
### Resumo

Tentei cobrir a maior parte de cenários e regras para verificação de permissões em um sistema, mas é uma parte muito diversa em cenários e contextos, então não tenho a presunção de ter conseguido cobrir todas as possibilidades e exceções. Para aprofundar mais, a documentação do próprio cancancan é um ótimo ponto de partida.

Sobre a lib em si, particularmente, na parte de verificações de permissões nos controllers é onde eu acho que o cancancan, com o uso do load\_and\_authorize\_resource em um controller centralizado, mais é vantajoso e ajuda a manter o foco de desenvolvedor nas funcionalidades, evitando a tarefa repetitiva de verificar as permissões em todas as actions de todos os controllers. Isso facilita também manutenção de problemas relacionados as funcionalidades do sistema, e melhora a legibilidade, pois os controllers ficam menores e somente com o código relativo a sua função.

Isso ajuda a evitar erros de permissões também, pois ao centralizar e evitar a repetição de código, a parte de autorização do sistema é executada de forma uniforme em todos os controller, inclusive o controle da exceção jogada pelo cancancan, permitindo termos o sistema inteiro lidando com o acesso não autorizado de forma uniforme e centralizada também.

Apesar de todos os helpers e convenções da biblioteca, ela é bem flexível e fácil de ser customizada caso necessário, permitindo o uso para praticamente qualquer situação e contexto sem muita dor de cabeça também

- [CanCanCommunity/cancancan](https://github.com/CanCanCommunity/cancancan/wiki/checking-abilities)
- [CanCanCommunity/cancancan](https://github.com/CanCanCommunity/cancancan/wiki/Fetching-Records)
- [CanCanCommunity/cancancan](https://github.com/CanCanCommunity/cancancan/wiki/changing-defaults)
- [CanCanCommunity/cancancan](https://github.com/CanCanCommunity/cancancan/wiki/authorizing-controller-actions)
- [CanCanCommunity/cancancan](https://github.com/CanCanCommunity/cancancan/wiki/Exception-Handling)

* * *