开发者

why is my CanCan Ability class overly permissive?

I'm (finally) wiring CanCan / Ability into my app, and I've started by writing the RSpec tests. But they're failing — my Abilities appear to be overly permissive, and I don't understand why.

First, the Ability class. The intention is that non-admin users can manage only themselves. In particular, they cannot look at other users:

class Ability
include CanCan::Ability

  def initialize(user)
    user ||= User.new           # create guest user if needed
    if (user.has_role?(:admin))
      can(:manage, :all)
    else
      can(:manage, User, :id => user.id)
    end
  end
end

The RSpec tests:

require 'spec_helper'
require 'cancan/matchers'

describe Ability do
  before(:each) do
    @use开发者_如何学Gor = User.create
  end

  describe 'guest user' do
    before(:each) do
      @guest = nil
      @ability = Ability.new(@guest)
    end

    it "should_not list other users" do
      @ability.should_not be_able_to(:read, User)
    end

    it "should_not show other user" do
      @ability.should_not be_able_to(:read, @user)
    end

    it "should_not create other user" do
      @ability.should_not be_able_to(:create, User)
    end

    it "should_not update other user" do
      @ability.should_not be_able_to(:update, @user)
    end

    it "should_not destroy other user" do
      @ability.should_not be_able_to(:destroy, @user)
    end
  end
end

All five of these tests fail. I've read the part of Ryan's documentation where he says:

Important: If a block or hash of conditions exist they will be ignored when checking on a class, and it will return true.

... but at most, that would only explain two of the five failures. So clearly I'm missing something fundamental.


I would expect this to work:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new           # create guest user if needed
    if (user.has_role?(:admin))
      can(:manage, :all)
    elsif user.persisted?
      can(:manage, User, :id => user.id)
    end
  end
end

I'm not sure what the behavior is defined to be if you pass :id => nil, which is what happens in the guest case, but at any rate, if you don't want the guest to access the list view, you shouldn't call can :manage, User for that user at all.

In general, I find that assigning user ||= User.new to make the ability harder to reason about.


Hey, apparently this should work, but some refactoring would help you to find the issue:

require 'spec_helper'
require 'cancan/matchers'

describe Ability do
  before(:each) { @user = User.create }

  describe 'guest user' do
    before(:each) { @ability = Ability.new(nil) }
    subject { @ability } # take advantage of subject

    it "should not be an admin user" do
      @user.should_not be_admin
      @user.should be_guest
    end

    it "should_not show other user" do
      should_not be_able_to(:read, @user)
    end

    it "should_not create other user" do
      should_not be_able_to(:create, User)
    end

    it "should_not update other user" do
      should_not be_able_to(:update, @user)
    end

    it "should_not destroy other user" do
      should_not be_able_to(:destroy, @user)
    end
  end
end

Note that also I removed this example @ability.should_not be_able_to(:read, User).

Hope it helps you.


I've got this bad habit of answering my own questions, but I give props to @jpemberthy and @Austin Taylor for pointing me in the right direction. First (and this is cosmetic), I added this to my User model:

class User
  ...
  def self.create_guest
    self.new
  end

  def guest?
    uninitialized?
  end
end

and cleaned up my Abilities model accordingly:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.create_guest
    if (user.admin?)
      <admin abilities here>
    elsif (user.guest?)
      <guest abilities here>
    else
      <regular user abilities here>
    end
  end
end

But the real fix was in my RSpec tests. Since User has validations on email and password fields, my original code of:

before(:each) do
  @user = User.create
end

was failing, thus creating an uninitialized @user. Since the :id field was nil, the Ability clause:

can(:manage, User, :id => user.id)

was succeeding with a guest user because nil == nil (if that makes sense). Adding the required fields to satisfy the User validations made (almost) everything work.

Moral: just as @jpemberthy suggested in his code, always include a test to make sure your user objects have the privileges that they are supposed to! (I still have another question regarding CanCan, hopefully less boneheaded than this one, appearing in a StackOverflow topic near you...)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜