CanCan + Devise for the devise user model - ruby-on-rails

Using devise, I have a User model. I do not have a user controller.
To use CanCan I need to do (below) at the top of my controllers
# Authorization w Devise & CanCan
before_filter :authenticate_user! # Devise, signed in users only
load_and_authorize_resource # CanCan
Where do I add this so I can have permissions for the User model given I have no user controller?
Thanks

You can add that code to any controller for which you need authentication, you don't need an UsersController
before_filter :authenticate_user!
this line require a valid user signed in with devise, so if you try to access a controller with this before_filter without being logged you'll be redirected by devise to the sign_in_path
load_and_authorize_resource # CanCan
this other line will fill an instance variable to a default value (if not already set) and then check your privileges using the Ability class, so assuming you have an ArticleController it will do the following behind the scenes (actual code is based on the current action)
# for the show action
#article = Article.find(params[:id])
raise CanCan::AccessDenied unless can(:read, #article)
The can(:read, #article) statement is the hearth of CanCan library, it will return a boolean value based on your ability class. Can read more on it here
If your whole application requires authentication you can simply add the before_filter :authenticate_user! line to the ApplicationController

Related

ActiveAdmin over ride after_sign_in_path_for method

I am using Devise gem for Authentication and ActiveAdmin gem which also uses Devise gem as dependency. I want to enable 2FA for ActiveAdmin, that's why I want to over ride after_sign_in_path_for method. I am able to over ride the method in ApplicationController but doesn't sounds right to me because this will affect our normal login as well. Is there any way to over ride after_sign_in_path_for only for ActiveAdmin. Currently this is how I am doing
class ApplicationController < ActionController::Base
def after_sign_in_path_for
end
end
If I can't only over ride only ActiveAdmin controller then how to deal with normal login without 2FA and ActiveAdmin login with 2FA. Something like this.
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
if resource.instance_of?(AdminUser)
redirect_to setup_2fa_path
else
super # <-------- is this possible or is this correct ?
end
end
end
There must be a way where I can only over ride after_sign_in_path_for for ActiveAdmin. Or do I need to over ride ActiveAdmin::Devise::SessionsController class in initializer? Putting controller login inside initializer also don't feel right to me.
UPDATE
I noticed there is one more problem, when control reaches after_sign_in_path then current_admin_user is already set. This is problem because user can skip setting up 2FA and type path in browser like localhost:3000/admin and they are in because there is current_admin_user. I can set current_admin_user to nil in after_sign_in_path but I am not sure if that will open door to other type of attacks ? How should I deal with this ? or shall I try to over ride different method ? I don't know if there is something like before_sign_in_path_for or something else.
An approach is to open up and extend the Devise::SessionsController within ActiveAdmin. I managed to implement 2fa with a one time password based on code discussed at https://medium.com/#acesubido/adding-two-factor-authentication-in-activeadmin-2ed134b60042 and https://medium.com/#acesubido/part-2-adding-two-factor-authentication-in-activeadmin-cc5eab67057c
Also: https://blog.kiprosh.com/adding-two-factor-authentication-2fa-for-activeadmin-auth-in-a-ruby-on-rails-web-application/ can be of inspiration, I chose for the first setup with a 2 step process. Also you may want to consider a before_action in you ApplicationController that allows for users to setup their 2fa after logging in:
def check_2fa
return if current_admin_user.nil?
redirect_to user_dashboard_two_factor_new_path \
if current_admin_user.force_2fa && !current_admin_user.otp_required_for_login
end
Good luck.

Sign out a devise user from a model

In my application, I need to sign out specific users from time to time. And I need to do it from the interface or from a sidekiq worker. So I would like to create a sign_out method in my user model.
I saw in the documentation that devise provides a sign_out method but only in the controller. Is there a way to access to this method from a model or something similar.
Thanks
You need to read this answer https://stackoverflow.com/a/24388643/4269732 first.
If I was to implement this behavior then I would have added a column in User Model like expire_at_next_request?
Then just check this value in before_filter and logout the user if this is true.
class ApplicationController < ActionController::Base
before_filter :logout_if_requested
def logout_if_requested
if current_user && current_user.expire_at_next_request?
current_user.update_attributes(:expire_at_next_request=>false)
sign_out current_user
redirect_to :new_session_path
end
end

skip authorization for specific controllers using pundit in rails 4

I am using rails 4, devise for authentication and Pundit for authorization. I have restricted my application to check for authorization on every controller by below code.
class ApplicationController < ActionController::Base
include Pundit
after_action :verify_authorized
#.....
end
However, i want to skip authorization for two specific controllers in my application (they are open to public, users do not need to sign in). How can i achieve it without removing verify_authorized in ApplicationController ?
skip_after_action :verify_authorized
I'm working with Rails 5 and I wanted to skip authorization in just one action but not the whole controller. So, what you can do according to the documentation is to use skip_authorization feature in the controller action as shown below:
class Admin::DashboardController < Admin::BaseController
def index
#organizers = Organizer.count
#sponsors = Sponsor.count
#brochures = Brochure.count
skip_authorization
end
def sponsors_approve
# some statements...
end
def organizers_approve
# some statements...
end
end
In this controller the only one action to be skipped is index, the other ones must be authorized.
I hope it could be useful for somebody else.

How to configure Devise+Cancan correctly for guest users

In a Rails 3.2 app I'm using Devise + CanCan. The app previously restricted access to only logged in users. I'm in the process of adding a Guest user/ability that will be able to read certain sections of the site.
I'm having trouble understanding the "correct" way to set this up, specifically what combination of before_filter :authenticate! and load_and_authorize_resource is needed in controllers.
While working on this I've stripped the ability class to a minimum.
#Ability.rb
class Ability
include CanCan::Ability
def initialize(user_or_admin)
user_or_admin ||= User.new
can :manage, :all
end
end
In a model-less/ static page Home controller
#home_controller.rb
class HomeController < ApplicationController
load_and_authorize_resource
def index
...some stuff
end
end
and
#application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate!
...more stuff
end
With this set up, un-logged-in users are redirected to Devise sign in page.
If I remove before_filter :authenticate! from the application controller I get an error uninitialized constant Home from activesupport-3.2.11/lib/active_support/inflector/methods.rb
If I remove load_and_authorize_resource from the home controller, this error goes away.
This is ok with my simplified testing Ability class, but as I start adding roles and abilities back in I will need to have CanCan handling the Home controller, i.e., will need load_and_authorize_resource to be called.
Can anyone help me understand why this error occurs when before_filter :authenticate! is removed, and point me towards any info that explain the "correct" way to set up Devise+Cancan for guest users. The info I've found thus far only explains how to set up the Ability class, not how to configure Devise.
The problem is that there is no resource to authorize. Therefore, you need only call authorize_resource not load_and_authorize_resource. See authorizing controller actions in the cancan documentation for further information.
Update: You must also specify the class as false: authorize_resource class: false.
Then your home controller will look like this:
class HomeController < ActionController::Base
authorize_resource class: false
def show
# automatically calls authorize!(:show, :home)
end
end
This information is in the Non-RESTful controllers section. Sorry about that.

Rails: Devise redirect to a stored location after sign in or sign up?

I'm using Devise in a Rails application I'm writing, and I want to let users go back to where they were after signing in or signing up.
For example, if I have a "comments" Controller that is protected by:
before_filter :authenticate_user!
Then I want users who click a "Comment Now!" button (and are therefore redirected to the new action in CommentsController) to log in and then have Devise redirect them to the new action (or wherever they were) in CommentsController, not to the generic root of the application, or to a generic after_sign_in_path.
Looking through the RDOC for Devise, I found this method that makes it look as if Devise has at least the capability to do something like this on its own, but I can't figure out a way.
OK, so I've done some more experimentation, and working with Kormie's info, I've got a working solution.
From what I can determine, before_filter authenticate_user! does not save the route for returning the user. What I did was this:
First, I added an extra before_filter at the top of my controller
before_filter :store_location
before_filter :authenticate_user!
Then, I wrote the store_location method at the bottom of the controller
private
def store_location
session[:user_return_to] = any_old_route_path
end
I don't claim this is perfect, but it works for me. (The downside for anyone else wanting to use it, is that it only supports one return path per controller. This is all I need for myself, but it is only a slight improvement over the one return path per app that I was using previously.) I would really appreciate anyone else's insights and suggestions.
Devise should do this by itself. The authenticate_user! filter also did not want to work for me when the route to the action was set via PUT method. When I have changed this to GET in routes.rb devise started to work as expected.
The simple way to do this:
# Modified from https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
stored_location_for(resource) || your_defaut_path
end
end
I think by default Devise saves the route but you may be usinging
sign_in #user
this should redirect you
sign_in_and_redirect(#user) #assuming you are sigining in that resource
Have you tried after_sign_in_path_for? If you define that method in your ApplicationController it should override the default implementation on a per controller basis.
Adapted from Devise Wiki how to:
Redirect back to current page after sign in, sign out, sign up, update
Redirecting back to the "current page" involves saving the current url in the session and then retrieving the url from the session after the user is authenticated / signed out. This should only really be done for GET requests as the other http methods (POST, PUT, PATCH, DELETE) are not idempotent and should not be repeated automatically.
To store the location for your whole application use before_action to set a callback (Use before_filter in Rails versions before 4.0).
This example assumes that you have setup devise to authenticate a class named User.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :store_user_location!, if: :storable_location?
# The callback which stores the current location must be added
# before you authenticate the user as `authenticate_user!` (or
# whatever your resource is) will halt the filter chain
# and redirect before the location can be stored.
before_action :authenticate_user!
# To redirect to the stored location after the user signs
# signs in you would override the after_sign_in_path_for method:
def after_sign_in_path_for(resource_or_scope)
# *My note, not wiki*: you may need a fall back as
# stored_location_for can return nil. I've added root_path
stored_location_for(resource_or_scope) || root_path
end
private
# Its important that the location is NOT stored if:
# - The request method is not GET (non idempotent)
# - The request is handled by a Devise controller
# such as Devise::SessionsController as that could
# cause an infinite redirect loop.
# - The request is an Ajax request as this can lead
# to very unexpected behaviour.
def storable_location?
request.get? && is_navigational_format? &&
!devise_controller? && !request.xhr?
end
def store_user_location!
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
end
Reference
Devise How To: Redirect back to current page after sign in, sign out, sign up, update

Resources