Cancan + Devise rescue_from not catching exception - ruby-on-rails

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.

Related

get :index Rspec error, during unit test for Static Index controller page

I'm currently attempting my first unit test and I'm receiving the following errors
Failures:
1) StaticPagesController GET #index responds successfully with an HTTP 200 status code
Failure/Error: get :index
NoMethodError:
undefined method `authenticate!' for nil:NilClass
# /Users/danielmayle/.rvm/gems/ruby-2.2.1/gems/devise-3.5.2/lib/devise/controllers/helpers.rb:112:in `authenticate_user!'
# ./spec/static_pages_controller_spec.rb:6:in `block (3 levels) in <top (required)>'
2) StaticPagesController GET #index renders the index template
Failure/Error: get :index
NoMethodError:
undefined method `authenticate!' for nil:NilClass
# /Users/danielmayle/.rvm/gems/ruby-2.2.1/gems/devise-3.5.2/lib/devise/controllers/helpers.rb:112:in `authenticate_user!'
# ./spec/static_pages_controller_spec.rb:6:in `block (3 levels) in <top (required)>'
Here is my unit test code:
require 'rails_helper'
describe StaticPagesController, :type => :controller do
context "GET #index" do
before do
get :index
end
it "responds successfully with an HTTP 200 status code" do
expect(response).to be_success
expect(response).to have_http_status(200)
end
it "renders the index template" do
expect(response).to render_template("index")
end
end
end
And here is my static_controller.rb code:
class StaticPagesController < ApplicationController
def index
end
def landing_page
#featured_product = Product.first
end
def thank_you
#name = params[:name]
#email = params[:email]
#message = params[:message]
UserMailer.contact_form(#email, #name, #message).deliver_now
end
end
Why do are these errors coming up and how do I fix the problem? I've only been coding for a few months so any assistance would be much appreciated. Thanks :)
Update
Here is my application controller
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
rescue_from CanCan::AccessDenied do |exception|
redirect_to main_app.root_url, :alert => exception.message
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :username
end
end
Here is my user_mailer code as well
class UserMailer < ActionMailer::Base
default from: "dmayle012#gmail.com"
def contact_form(email, name, message)
#message = message
mail(:from => email,
:to => 'dmayle012#gmail.com',
:subject => "New ActionMail Message from #{name}")
end
end
Your problem is this line:
before_action :authenticate_user!
This makes devise to check authorisation for current_user, which is nil in your test. There are two ways to fix it depending on what your requirements are.
Firstly, if you want any internet user to be able to view your static pages without login, add:
skip_before_action :authenticate_user!
in your StaticPagesController. This will tell devise that you don't require current_user to be allowed to view the page (not really - it doesn't tell devise anything, it just not asking it to authorise user).
Second option - you need user to be logged in to view those pages. In this case you need to create fake session before you start your test using some helper methods provided by devise. This is very easy and well documented process, you can find the steps here: https://github.com/plataformatec/devise/wiki/How-To:-Test-controllers-with-Rails-3-and-4-(and-RSpec)#controller-specs. Not adding those steps in the answer as I believe you will go with first case (since every page requires some pages available without the session, at least for the login page).
Devise has some helper methods specifically for this. Here is how to setup your controller spec to get past your undefined method authenticate_user! for nil error
First you need to include them in your rails_helper.rb like this
# spec/rails_helper.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
end
And then you can use the helper like this in your static pages controller
# spec/controllers/static_pages_controller_spec.rb
describe StaticPagesController, :type => :controller do
context "GET #index" do
before :each do
user = FactoryGirl.create(:user, approved: true)
sign_in :user, user
get :index
end
...
end
end

Devise - possible to use controller test to test user NOT signed in?

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.

Stub current_user when testing devise views

I am testesting my custom devise views with capybara and i need a logged in user in order to tests some views, for example:
feature 'user/edit' do
describe "the edit profile process", :type => :feature do
include Devise::TestHelpers
let!(:user){ FactoryGirl.build(:user)
def sign_in(user)
if user.nil?
request.env['warden'].stub(:authenticate!).
and_throw(:warden, {:scope => :user})
controller.stub :current_user => nil
else
request.env['warden'].stub :authenticate! => user
controller.stub :current_user => user
end
end
it "update " do
sign_in(user)
visit '/users/edit'
within("#edit_user") do
#=> something
end
click_on 'Update'
page.should have_content("something")
end
end
end
But i got this error
Failure/Error: request.env['warden'].stub :authenticate! => user
NoMethodError: undefined method `env' for nil:NilClass
The module Devise::TestHelpers establishes the #request instance variable. You are referencing request, which is apparently set to nil in some other accessible scope, resulting in the error you're getting.
See https://github.com/plataformatec/devise/blob/b8ed2f31608eccb6df6d5bb2e66238d6dfc4bcfc/lib/devise/test_helpers.rb for the source of TestHelpers.

RSpec stub not working for integration test

Inside a controllers test, I want to test that when logged in, the controller renders the request fine, else if not logged in, it redirects to the login_path.
The first test passes fine as expected, no user is logged in, so the request is redirected to the login_path. However I've tried a myriad of stub/stub_chain's but still can't get the test to fake a user being logged in and render the page okay.
I would appreciate some direction on getting this to work as expected.
The following classes and tests are the bare bones to keep the question terse.
ApplicationController
class ApplicationController < ActionController::Base
include SessionsHelper
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
SessionsHelper
module SessionsHelper
def logged_in?
redirect_to login_path, :notice => "Please log in before continuing..." unless current_user
end
end
AppsController
class AppsController < ApplicationController
before_filter :logged_in?
def index
#title = "apps"
end
end
apps_controller_spec.rb
require 'spec_helper'
describe AppsController do
before do
#user = FactoryGirl.create(:user)
end
describe "Visit apps_path" do
it "should redirect to login path if not logged in" do
visit apps_path
current_path.should eq(login_path)
end
it "should get okay if logged in" do
#stubs here, I've tried many variations but can't get any to work
#stubbing the controller/ApplicationController/helper
ApplicationController.stub(:current_user).and_return(#user)
visit apps_path
current_path.should eq(apps_path)
end
end
end
This is not working because you are stubbing the method current_user on the ApplicationController class, and not an instance of that class.
I would suggest stubbing it (correctly) on an instance of that class, but your test appears to be an integration test rather than a controller test.
What I would do instead then is as Art Shayderov mentioned is to emulate the sign-in action for a user before attempting to visit a place that requires an authenticated user.
visit sign_in_path
fill_in "Username", :with => "some_guy"
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("You have signed in successfully.")
In my applications, I've moved this into a helper method for my tests. This is placed into a file at spec/support/authentication_helpers.rb and looks like this:
module AuthenticationHelpers
def sign_in_as!(user)
visit sign_in_path
fill_in "Username", :with => user.username
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("You have signed in successfully.")
end
end
RSpec.configure do |c|
c.include AuthenticationHelpers, :type => :request
end
Then in my request specs, I simply call the method to sign in as that particular user:
sign_in_as(user)
Now if you want to sign in using a standard controller test, Devise already has helpers for this. I generally include these in the same file (spec/support/authentication_helpers.rb):
RSpec.configure do |c|
c.include Devise::TestHelpers, :type => :controller
end
Then you can sign in using the helpers like this:
before do
sign_in(:user, user)
end
it "performs an action" do
get :index
end
I would look at http://ruby.railstutorial.org/chapters/sign-in-sign-out#sec:a_working_sign_in_method.
The author describes how to write a sign_in method and use it in your rspec tests.
It doesn't look like controller test. It looks more like rspec-rails request spec which simulates browser. So stabbing controller won't work, you have to either simulate sign in (something like this)
visit sign_in
fill_in 'username', :with => 'username'
...
or manually add user_id to session.
If on the other hand you want to test controller in isolation your test should look like that:
get 'index'
response.should be_success

How to stub current_user method in Rspec

I have a simple user login system:
module SessionsHelper
def logged_in?
current_user.present?
end
def current_user
#current_user ||= begin
if session[:current_user_id]
User.find(session[:current_user_id]) rescue nil
end
end
end
end
which I include in ApplicationController:
class ApplicationController < ActionController::Base
include SessionsHelper
helper SessionsHelper
I am trying to test the banners controller:
require 'spec_helper'
describe Admin::BannersController do
describe 'POST create' do
let(:user){ create(:user_admin) }
before do
controller.stub(:current_user){ user }
end
it "create action should render new template when model is invalid" do
Banner.any_instance.stubs(:valid?).returns(false)
post :create
response.should render_template(:new)
end
:user_admin is the properly set up Factory Girl admin user.
However the test still says: You are not authorized to access this page.
This is from Cancan.
Did I not stub it properly? Thanks.
def fake_user
user = FactoryGirl.build(:user, :id => 100000 + rand(100000))
stub(controller).current_user { user }
user
end

Resources