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

Foi útil?

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top