Cancan não mostra elementos de visão autorizados
-
26-09-2019 - |
Pergunta
Estou tentando obter alguma autenticação/autorização básica com o Devise/Cancan com Rails. Em vez de usar funções como o screencast de Ryan B e outros exemplos ao redor, estou tentando fazer algo básico:
1 - um usuário pode fazer login
2 - Um usuário só pode editar/destruir seus próprios artigos (sem papéis, você está conectado e pode criar novos artigos e editar/destruir o seu próprio ou está conectado e só pode ver artigos e login)
Estou usando o Devise para a primeira parte e isso está funcionando bem, mas não consigo que a segunda parte trabalhe com a CANCAN. Os links de edição e destruição para os artigos não aparecem quando você está conectado e o URL direto (por exemplo, artigos/3/editar) ainda permite, mesmo que o artigo seja para outro usuário.
Meu ability.rb
é
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.nil?
can :read, :all
else
# can :manage, :all #test - with this, all the edit/destroy links appear
can :manage, Article, :user_id == user
end
end
end
articles_controller.rb
:
class ArticlesController < ApplicationController
before_filter :authenticate_user!, :except => [:index, :show] # for Devise
load_and_authorize_resource
# GET /articles
# GET /articles.xml
def index
@articles = Article.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @articles }
end
end
# GET /articles/1
# GET /articles/1.xml
def show
@article = Article.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @article }
end
end
# GET /articles/new
# GET /articles/new.xml
def new
@article = Article.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @article }
end
end
# GET /articles/1/edit
def edit
@article = Article.find(params[:id])
end
# POST /articles
# POST /articles.xml
def create
@article = Article.new(params[:article])
@article.user = current_user
respond_to do |format|
if @article.save
format.html { redirect_to(articles_path, :notice => 'Article was successfully created.') }
format.xml { render :xml => articles_path, :status => :created, :location => articles_path }
else
format.html { render :action => "new" }
format.xml { render :xml => @article.errors, :status => :unprocessable_entity }
end
end
end
# PUT /articles/1
# PUT /articles/1.xml
def update
@article = Article.find(params[:id])
respond_to do |format|
if @article.update_attributes(params[:article])
format.html { redirect_to(@article, :notice => 'Article was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @article.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /articles/1
# DELETE /articles/1.xml
def destroy
@article = Article.find(params[:id])
@article.destroy
respond_to do |format|
format.html { redirect_to(articles_url) }
format.xml { head :ok }
end
end
end
e a visualização parcial que lista artigos _article_list.html.erb
:
<table>
<tr>
<th>Title</th>
<th>Description</th>
<th>User</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.description %></td>
<td><%= article.user_id %></td>
<td><%= link_to 'Show', article %></td>
<% if can? :update, @article %>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<% end %>
<% if can? :destroy, @article %>
<td><%= link_to 'Destroy', article, :confirm => 'Are you sure?', :method => :delete %></td>
<% end%>
</tr>
<% end %>
</table>
Com esta configuração, os links de edição/destruição na vista não aparecem, a menos que haja um cobertor can :manage, :all
, até can :manage, Article
não funciona. Como mencionei acima, ele também não está restringindo as ações reais, pois você é capaz de links profundos diretamente à edição de um artigo e permite.
Não tenho certeza do que estou fazendo de errado aqui. Seria ótimo obter alguma ajuda.
desde já, obrigado
Jason
Solução
Consegui resolver meu problema. Eu redefini meu ambiente (RVM - resintalizei as gemas e pedras preciosas - Ruby 1.9.2 e Rails 3.0.0) e mudei parte do código e todos os problemas que eu estava tendo desaparecido (Redirect Loop, visualizar elementos que não estão mudando com base em serem registrados em ações do controlador não autorizadas ainda permitidas). Eu colei ability.rb
, articles_controller.rb
, e _article_list.html.erb
.
ability.rb
:
class Ability
include CanCan::Ability
def initialize(user)
if user
can :create, Article
can :read, :all
can :update, Article, :user_id => user.id
can :delete, Article, :user_id => user.id
else
can :read, :all
end
end
end
Eu acho que faz sentido agora, mas como apenas a atualização e exclusão deveriam ser para os artigos do usuário atual, divida os elementos CRUD para serem específicos.
articles_controller.rb
class ArticlesController < ApplicationController
before_filter :authenticate_user!, :except => [:index, :show]
# load_and_authorize_resource # RESTful automated CanCam authorization - excludes non RESTful
# GET /articles
# GET /articles.xml
def index
@articles = Article.all
authorize! :read, @articles
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @articles }
end
end
# GET /articles/1
# GET /articles/1.xml
def show
@article = Article.find(params[:id])
authorize! :read, @article
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @article }
end
end
# GET /articles/new
# GET /articles/new.xml
def new
@article = Article.new
authorize! :create, @article
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @article }
end
end
# GET /articles/1/edit
def edit
@article = Article.find(params[:id])
authorize! :update, @article
end
# POST /articles
# POST /articles.xml
def create
@article = Article.new(params[:article])
@article.user = current_user
authorize! :create, @article
respond_to do |format|
if @article.save
format.html { redirect_to(articles_path, :notice => 'Article was successfully created.') }
format.xml { render :xml => articles_path, :status => :created, :location => articles_path }
else
format.html { render :action => "new" }
format.xml { render :xml => @article.errors, :status => :unprocessable_entity }
end
end
end
# PUT /articles/1
# PUT /articles/1.xml
def update
@article = Article.find(params[:id])
authorize! :update, @article
respond_to do |format|
if @article.update_attributes(params[:article])
format.html { redirect_to(@article, :notice => 'Article was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @article.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /articles/1
# DELETE /articles/1.xml
def destroy
@article = Article.find(params[:id])
@article.destroy
authorize! :delete, @article
respond_to do |format|
format.html { redirect_to(articles_url) }
format.xml { head :ok }
end
end
def by
@user = User.find(params[:id])
@articles = @user.articles
authorize! :read, @articles
end
end
load_and_authorize_resource
Funciona, mas eu coloquei autorização específica! Linhas em cada ação do controlador, pois tenho uma ação extra na parte inferior. Ambos agora funcionam.
Atualizei a referência ao @Article para o artigo para referenciar o artigo atual da lista em _article_list.html.rb
:
<table>
<tr>
<th>Title</th>
<th>Description</th>
<th>User</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.description %></td>
<td><%= article.user_id %></td>
<td><%= link_to 'Show', article %></td>
<% if can? :update, article %>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<% end %>
<% if can? :delete, article %>
<td><%= link_to 'Destroy', article, :confirm => 'Are you sure?', :method => :delete %></td>
<% end %>
</tr>
<% end %>
</table>
Tudo funcionando agora. Obrigado pela ajuda aqui e espero que isso ajude outra pessoa se eles se deparar com esse problema.
Outras dicas
Sua condição para combinar um ID de usuário não está certa. Deveria ser:
can :manage, Article, :user_id => user.id
O atributo que você deseja verificar é mapeado para o valor que deseja verificar.
Além disso, você está verificando user.nil?
Quando não pode ser nulo porque você acabou de inicializar. (Provavelmente um sintoma de ter tentado muitas coisas!)
Sua captura funciona? Se você descomentar a lata: gerenciar ,: toda a linha um usuário poderá editar seu post (junto com todos os outros, é claro)?
Você já tentou mudar, pode: gerenciar, artigo: user_id == usuário para
can :manage, Article do |article|
article.try(:user) == user
Eu nunca consegui carregar um autorizado a trabalhar- embora eu suspeite que estava fazendo algo errado. Para impedir que alguém acesse o URL diretamente, na ação de edição do seu artigo, tente adicionar isso
unauthorized! if cannot? :edit, @article