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
Related
when I attempt to navigate to /admin. Cancancan raises an execption if there is no user logged in and if there is a non admin user logged in. I would like to be redirected to a login screen - if the log in is successful, proceed to the rails_admin dashboard.
See below, my present code/config, please let me know if you need more info:
/app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user && user.admin?
can :access, :rails_admin # only allow admin users to access Rails Admin
can :dashboard
can :manage, :all
else
can :read, :all # allow everyone to read everything
end
end
end
/config/initializers/rails_admin.rb
RailsAdmin.config do |config|
### Popular gems integration
# == Devise ==
# config.authenticate_with do
# warden.authenticate! scope: :user
# end
# config.current_user_method(&:current_user)
# == Cancan ==
config.authorize_with :cancan
## == Pundit ==
# config.authorize_with :pundit
## == PaperTrail ==
# config.audit_with :paper_trail, 'User', 'PaperTrail::Version' # PaperTrail >= 3.0.0
### More at https://github.com/sferik/rails_admin/wiki/Base-configuration
config.actions do
dashboard # mandatory
index # mandatory
new
export
bulk_delete
show
edit
delete
show_in_app
## With an audit adapter, you can add:
# history_index
# history_show
end
config.model 'User' do
create do
field :name
field :email
field :password
field :password_confirmation
field :role
end
edit do
field :name
field :email
field :password
field :password_confirmation
field :role
end
end
end
/config/routes.rb
Rails.application.routes.draw do
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
# authenticate :admin do
# mount RailsAdmin::Engine => '/admin', as: :rails_admin
# end
root to: 'visitors#index'
devise_for :users
resources :users
devise_scope :user do
get "signup", to: "devise/registrations#new"
get "login", to: "devise/sessions#new"
get "logout", to: "devise/sessions#destroy", as: 'logout'
end
end
It sounds like you're looking to rescue from a CanCan::AccessDenied Exception.
rescue_from CanCan::AccessDenied do |exception|
redirect_to login_path, :alert => exception.message
end
Replace login_path with the actual path name of your login. You an find it by doing a rake routes and finding the entry that's pointing to devise/sessions#new
You can also make the logic more robust and only redirect if they are trying to visit a page in the admin area.
I had similar situation, and I was able to redirect no admin to root path. I'm using Rails 5 and Ruby 2.3 Here is my settings:
admin rails
# config/initialize/rails_admin.rb
config.authorize_with do
unless current_user.try(:admin?)
flash[:error] = "You are not authorize to access this page!"
redirect_to main_app.root_path
end
end
CanCanCan
# models/ability.rb
def initialize(user)
user ||= User.new # guest user (not logged in)
can :manage, :all if user.role == "user"
end
When a user try to access /admin it gets redirect to root path with a flash error.
I have a controller that depends on the user being authenticated. So it looks like this
class PlansController < ApplicationController
before_action :authenticate_user!
def create
puts "here"
if user_signed_in?
puts "true"
else
puts "false"
end
end
end
My controller tests are working just fine when teh user IS signed in, i.e., when I'm writing something like this:
require 'rails_helper'
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
describe "create action" do
before do
#user = User.create(...)
sign_in :user, #user
end
it "should puts here and then true" do
post :create
# => here
# => true
end
end
But I'd also like to test what happens in the else statement. Not sure how to do this, it fundamentally doesn't even put the here. Is it possible to test this? Or should I just leave and let Devise be?
describe "create action" do
before do
#user = User.create(...)
# do not sign in user (note I have also tried to do a sign_in and then sign_out, same result)
end
it "should puts here and then true" do
post :create
# => nothing is put, not even the first here!
# => no real "error" either, just a test failure
end
end
The before_action :authenticate_user! will immediately redirect you to the default sign-in page, if the user isn't signed in, skipping the create action altogether.
The if user_signed_in? statement is moot in this case, because the user will always be signed in when that code has the chance to run.
If plans can be created with or without an authenticated user, remove the before_action line.
I have a Rails 3.2 app with Devise and CanCan. User abilities are defined by roles. There are five Roles in the database with id values 1-5, and a User has_one role through a Membership. So, for example, Membership.create(user_id: 2, role_id: 4) will lead to User.find(2).role == Role.find(4) # => true
My ability file looks like
if user.has_role? :admin
can :manage, Model
can :manage, AnotherModel
# etc.
elsif user.has_role? :basic
can :read, Model
can :read, AnotherModel
elsif user.has_role? (etc. for other roles)
(etc.)
else
cannot :manage, :all
I want to kick out people who are attempting to access pages they are not authorized for. My application controller includes:
rescue_from CanCan::AccessDenied do |exception|
reset_session
redirect_to new_user_session_path
end
This behavior works properly when clicking around in development—-a basic user created via rails console gets kicked out when trying to do admin things, and a user with no roles (created in console, then User.last.membership.destroy so that User.last.role # => nil) cannot log in to the dashboard--instead the user is immediately redirected to the login page (a User with no role cannot manage anything).
I'm having problems when trying to test this same behavior, specifically for users with no role:
# spec/support/login_helpers.rb
def login_via_sign_in_page(user)
visit new_user_session_path
fill_in :user_email, with: user.email
fill_in :user_password, with: user.password
click_button 'Sign in'
end
# spec/features/dashboard_spec.rb
describe "as a user with no role" do
let(:user) { FactoryGirl.create(:user) }
before { login_via_sign_in_page(user) }
it "should redirect to the sign_in page" do
current_path.should == new_user_session_path
end
end
The above test fails, with RSpec showing that the user successfully made it to the dashboard page instead of being redirected to the sign in page. However, the following specs do pass:
# spec/features/dashboard_spec.rb
describe "as a guest browser (no login whatsoever)" do
it "should redirect to the sign_in page" do
visit dashboard_index_path
current_path.should == new_user_session_path
end
end
describe "as an admin user" do
let(:user) { FactoryGirl.create(:admin_user) }
before { login_via_sign_in_page(admin_user) }
it "should go to the dashboard" do
visit dashboard_index_path
current_path.should == dashboard_index_path
end
end
...as well as things like basic user getting redirected to the sign_in page when attempting admin actions
I have read this post, which seems to be dealing with a similar issue, but the suggestions there have not helped.
Finally, I get confusing results when using debugger and save_and_open_page. The former will give me user # => <A legit User object> and user.role # => nil. However, save_and_open_page shows me logged in on the dashboard page.
Have you tried including the Warden::Test::Helpers, and the rest of the config required for testing Devise with Capybara? Take a look here How To: Test with Capybara. Which driver are you using?
I am implementing Devise and Cancan for user authentication and permissions. Everything works great so far except I am not able to redirect users to the login page when they are not allowed to access a specific feature.
My test is:
feature 'A signed in user' do
before(:each) do
user = FactoryGirl.create(:user)
visit "/login"
fill_in "user_email", :with => user.email
fill_in "user_password", :with => "ilovebananas"
click_button "Sign in"
end
scenario 'should not have access to admin dashboard' do
visit '/admin'
page.should have_content 'Log in'
end
end
And I get the following failure:
Failures:
1) A signed in user should not have access to admin dashboard
Failure/Error: visit '/admin'
CanCan::AccessDenied:
You are not authorized to access this page.
To be clear, all my permission management works as expected so far, except the redirection to login page.
Here is how things are set up:
ApplicationController:
check_authorization :unless => :devise_controller? # Cancan
rescue_from CanCan::AccessDenied do |exception|
redirect_to login_path, alert: exception.message
end
UsersController
class UsersController < ApplicationController
load_and_authorize_resource # Cancan
def queue
...
end
...
end
AdminsController
class AdminController < ActionController::Base
authorize_resource :class => false # Cancan, used because AdminController doesn't have an associated model
...
end
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user, not logged in
can :queue, User
if user.has_permission? :super_admin
can :manage, :all
elsif user.has_permission? :network_admin
end
end
end
What am I missing?
you must pass class name as string. try quoting it. or try
rescue_from CanCan::AccessDenied , :with => :login_page
private
def login_page
redirect_to login_path
end
You should to add "controller.authorize_resource" to admin/register.if abilities without conditions.
controller.authorize_resource
Example: can :manage, :all
If conditions are,
controller do
load_and_authorize_resource :except => [:update,:index, :show, :edit]
def scoped_collection
end_of_association_chain.accessible_by(current_ability)
end
end
Example: can :manage, Master::Country, :organization_branch_id => each_branch.id
i hope it help you
My Admins Controller was not < ApplicationController, so it did not load the ApplicationController rescue_from method.
Making the change solved my issue.
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