I am currently using cancan with rspec.
Please take a look at my ability.rb
require 'spec_helper'
require "cancan/matchers"
class Ability
include CanCan::Ability
def initialize(user)
if user # Only logged in users
if user.role? :admin
can :manage, :all
elsif user.role? :producer
can :read, Business
can :update, Business do |b|
b.user_id == user.id
end
can :redeem, Purchase
elsif user.role? :consumer
can :your, Deal
can [:create, :redirect_to_wepay], Purchase
can :show, Purchase do |purchase|
purchase.user_id == user.id
end
end
# Good thing about devise with Cancan is that it takes care of this.
can :manage, User do |the_user|
the_user.id == user.id
end
else
# This is needed for the cans that follows
user = User.new
end
# Everyone's session
can :read, Deal
can :read, Business
# You have to enable it for wepay
can [:sold_out, :callback, :received], Purchase
end
end
In my spec/models/ability_spec.rb I have
describe Ability do
describe "consumers" do
describe "cancan" do
before(:each) do
#user = Factory(:user, :role => "consumer")
#ability = Ability.new(#user)
end
describe "success" do
#**This line I am getting ability is nil
#ability.should == 5
#**This line gives me be_able_to undefined
##ability.should_not be_able_to(:read, Factory(:deal))
##ability.can(:read, Factory(:business)).should be_true
end
Any ideas why I am getting #ability as nil?
In addition, I want to put some of my controller's actions that are related to permission control in this ability_spec.rb file. Is that possible? (I explicitly want to achieve this because my app has 3 roles of users and I find myself littering my controllers spec files with all these permission related one liners.
Thanks!
Tests must appear in it or specify blocks. describe and context are simply for grouping.
describe "success" do
#**This line I am getting ability is nil
#ability.should == 5
end
Should be more like:
it "allows consumers to do blah blah blah" do
#ability.should == 5
end
Related
I have this in models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role? :registered
can :read Post
end
end
When I do this on rails console
#this returns a user with a role: "registered" attribute
user = User.first
post = Post.first
ability = Ability.new(user)
### This returns false ###
ability.can?(:read, post)
#=> false
This spec I have written to test the ability also fails while i expect it to pass.
describe User, :type => :model do
let(:post) {create(:post)}
describe "abilities" do
subject(:ability){Ability.new(user)}
let(:user){nil}
context "when is a registered user" do
## the default value for the role attribute in the user factory is "registered"
let(:user) {create(:user)}
it {is_expected.to be_able_to :read, post}
end
end
end
I can access and read posts in both /posts and /posts/:id when I am authenticated as a registered user on the browser, I have no idea why it is failing in both rails console and rspec.
Following our discussion, we concluded that the problem is either
Rails didn't load the Ability class, or
A code somewhere somehow overrides the Ability class.
The workaround-solution is to manually load the Ability file by appending the following at the end of the application.rb
require "#{Rails.root}/app/models/ability.rb"
I'm using CanCanCan with Rolify and I´m trying to test my Ability class authorization.
When testing if a unprivileged user can CRUD other users in the system the test fails
1) Ability a guest user should not be able to manage others
Failure/Error: expect(subject).to_not be_able_to(:crud, User)
expected not to be able to :crud User(...)
But I can't find any reason why the check in my Ability class fails:
class Ability
include CanCan::Ability
def initialize(user = User.new)
alias_action :create, :read, :update, :destroy, :destroy_multiple, to: :crud
# What is wrong?
can :crud, User, id: user.id
if user.has_role?(:admin)
can :manage, User
end
end
end
This is my spec:
require 'rails_helper'
require 'cancan/matchers'
RSpec.describe Ability do
let(:user) { create(:user) }
subject { Ability.new(user) }
context "a guest user" do
it "should be able to manage self" do
expect(subject).to be_able_to(:crud, user)
end
it "should not be able to manage others" do
expect(subject).to_not be_able_to(:crud, User)
end
end
end
expect(subject).to_not be_able_to(:crud, User)
You are referencing User model, not instance there. Use User.new or another persisted User instance.
I am trying to test a CanCan ability in my app that also uses Authlogic. I have verified the correct behavior works when using the actual site, but I want to write a functional test that will alert me if this behavior breaks in the future. My ability file is simple, and looks as follows:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
can :read, User
can :manage, User, :id => user.id
cannot :create, User
can :destroy, UserSession
if user.role? :guest
can :create, UserSession
cannot :destroy UserSession
end
end
end
My test for the UserSessionsController is also simple, and looks like this:
test "should redirect new for member" do
default_user = login :default_user
assert default_user.role? :member
assert_raise(CanCan::AccessDenied) { get :new }
assert_redirected_to root_path
end
Just for reference, my test_helper.rb looks like this:
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'authlogic/test_case'
class ActiveSupport::TestCase
fixtures :all
setup :activate_authlogic
def login(user_login)
UserSession.create users(user_login)
users(user_login)
end
end
When I run my code, my test fails, however:
test_should_redirect_new_for_member FAIL
CanCan::AccessDenied expected but nothing was raised.
Assertion at test/functional/user_sessions_controller_test.rb:13:in `block in <class:UserSessionsControllerTest>'
If I comment out the assert_raise, the redirect assertion also fails. Does anyone see anything wrong with my code that is causing this test to fail?
The problem was that I was rescuing the AccessDenied in my ApplicationController, so the exception was never being raised.
You need to block new action too.
if !(user.role? :member)
can :new, User
end
May be User having 'member' role have access to new action(display form for user) and restricted access to create action.
And one more thing, we don't need to use
cannot [:any_action], [Model]
We can do everything by can itself.
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
#user = 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...)
I'm trying to write up some cucumber tests to ensure cancan permissions are set correctly, and I'm having an odd problem:
When I log in through the following code, capybara says I've logged in as expected. However, when I then go to a resource which requires the given login, I get CanCan's "not authorized" message. Capybara prints out "logged in as testsuperadmin with role superadmin" (the desired role) on the very same "denied access" page.
Accessing the same page manually, not through cucumber/capybara, authorization is granted & everything works fine. Authentication is handled by devise.
I've tried adding #allow-rescue above the scenario and ActionController::Base.allow_rescue = true to features/support/env.rb - neither had any effect.
Any suggestions? This one really has me stumped.
Cheers...
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role? :superadmin
can :manage, :all
elsif user.role? :admin
can :manage, [Broker, User]
elsif user.role? :staff
can :manage, Broker
elsif user.role? :broker
can :manage, Broker, :user_id => user.id
can :read, Broker
elsif user.role? :customer
can :manage, User, :id => user.id
else can :read, [Broker]
end
end
end
# features/brokers.feature
#allow-rescue
Scenario: Successfully create Broker
Given I am logged in as "testsuperadmin" with password "testpassword"
When I go to the create broker page
Then show me the page # Authorization denied here, but signed in successfully if this line moved between "Given I am logged in" ... and "When I go to to create broker page"
......
# features/steps/devise_steps.rb
Given /^I am logged in as "([^\"]*)" with password "([^\"]*)"$/ do |username, password|
#visit path_to(sign in page)
visit "/users/sign_out"
visit "/users/sign_in"
fill_in("user[username]", :with => username)
fill_in("user[password]", :with => password)
click_button("Sign in")
end
# app/controllers/brokers_controller.rb
class BrokersController < ApplicationController
load_and_authorize_resource
# ...
def new
#broker = Broker.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #broker }
end
end