I have an app using devise, cancan and rolify that redirects on login to a page specific to their roles. What I am trying to figure out is how I can link to that same path elsewhere in the app, not just as a redirect?
class ApplicationController < ActionController::Base
protect_from_forgery
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_path, :alert => exception.message
end
def after_sign_in_path_for(resource)
case current_user.roles.first.name
when 'admin'
users_path
when 'yin'
content_yin_path
when 'yang'
content_yang_path
when 'yin_yang_lite'
content_yin_yang_lite_path
when 'yin_yang_pro'
content_yin_yang_pro_path
else
root_path
end
end
end
Basically, I am looking for how to get to the current_user => role => path, or current_user_role_path
You can define any controller method to also act as a helper method using the helper_method macro in the controller. For your case:
ApplicationController
helper_method :after_sign_in_path_for
Related
My user model has birthday and I'm trying to redirect to the edit page if it is blank after signing in with Facebook. I tried overriding the after_sign_in_path for resource but keep getting this error:
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
My Application controller:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def after_sign_in_path_for(resource)
stored_location_for(resource) ||
if resource.birthday.blank?
redirect_to edit_user_path(resource)
end
super
end
end
and my Omniauth controller:
class OmniauthCallbacksController < ApplicationController
skip_before_filter :authenticate_user!
def provides_callback_for
user = User.from_omniauth(env["omniauth.auth"], current_user)
if user.persisted?
flash[:notice] = "You have signed in!"
sign_in_and_redirect(user)
else
session['devise.user_attributed'] = user.attributes
redirect_to new_user_registration_url
end
end
def failure
flash[:notice] = "Something went wrong!"
redirect_to root_path
end
alias_method :facebook, :provides_callback_for
end
This should do the trick:
def after_sign_in_path_for(resource)
if resource.birthday.blank?
edit_user_registration_url
else
super
end
end
I have defined my own method authorize_user in one of my controllers, as:
def authorize_user
if !((current_user.has_role? :admin, #operator) || (current_user.has_role? :super_admin))
raise CanCan::AccessDenied
end
end
I want to rescue from the CanCan exception (or any other exception for that matter). I have used Rolify in my app. How do I rescue and redirect to the root_url of my app with a custom message?
I have tried the following options, but none of them worked:
Try 1:
rescue CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
end
Error in this case: syntax error, unexpected keyword_do, expecting '('
Try 2:
rescue CanCan::AccessDenied
redirect_to root_url, :alert => "Unauthorized Access"
Error in this case: Render and/or redirect were called multiple times in this action
How do I solve this issue?
This is my controller code:
class CabsController < ApplicationController
before_action :set_cab, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
after_action :authorize_user
# Some basic CRUD actions
private
def set_cab
#cab = Cab.find(params[:id])
#operator = Operator.find(params[:operator_id])
end
def cab_params
params.require(:cab).permit(:category, :number)
end
def authorize_user
if !((current_user.has_role? :admin, #operator) || (current_user.has_role? :super_admin))
raise CanCan::AccessDenied
end
end
end
I think you could try the rescue_from method.
For example, your ApplicationController, would look like this:
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied, with: :not_authorized
#other stuff
private
def not_authorized
redirect_to root_url, alert: "Unauthorized Access"
end
end
Since the question was updated with more code, here is additional information:
Some suggestions:
Make the :authorize_user a before_action as well. That way you don't need to worry about code running in the action even when the user was not allowed to do stuff.
You might also need to add the same :only option as for the :set_cab since you use the #operator instance variable.
Last, a personal code style preference is that I would have changed the if ! to unless to increase reading flow.
Try redirect_to(...) and return.
Agreeing with Jakob W I would like to point, that authorization (and authentication) MUST be performed only before action. What is the purpose of any authorization and exception raising when DB transaction, reading/writing to filesystem etc have been already done?
And using before_action has no problem with Render and/or redirect were called multiple times in this action - there will be only one redirect - in exception handling before controller method call.
So, I recommend next code (updated Jakob W's sample):
class CabsController < ApplicationController
#...
before_action :authorize_user
private
#...
def authorize_user
if !((current_user.has_role? :admin, #operator) || (current_user.has_role? :super_admin))
raise CanCan::AccessDenied
end
end
end
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied, with: :not_authorized
#other stuff
private
def not_authorized
redirect_to(request.referrer || root_path), alert: "Unauthorized Access"
end
end
Could I recommend another authorization gem? I think this one is flexible and easy to use - pundit (https://github.com/elabs/pundit). Page on github has some useful tips on authorization.
I am rolling R Bates authentication from scratch from here, and I'm wanting to put the call to the authorize method in the application controller. Basically I want the entire app locked down. Here is the app controller...
class ApplicationController < ActionController::Base
before_filter :authorize
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def authorize
redirect_to login_url, alert: "Not authorized" if current_user.nil?
end
end
But the probably is I'm getting an infinite loop in my URL call. How should I hand this?
sessions controller
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: "Logged in!"
else
flash.now.alert = "Email or password is invalid"
render "new"
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Logged out!"
end
end
You can skip before_filter (like in #vee answer) for some actions of SessionsController:
skip_before_filter :authorize, only: [:new, :create]
Alternatively you can modify authorize method to avoid redirects in some cases:
def authorize
return if skip_authorization?("#{controller_name}##{action_name}")
redirect_to login_url, alert: "Not authorized" if current_user.nil?
end
def skip_authorization?(location)
%w(sessions#new sessions#create).include?(location)
end
The loop is because of the redirect_to login_url....
You should skip the authorize filter in controller that has the login action defined as:
class SessionsController < ApplicationController
skip_before_filter :authorize, only: :login
def login
...
end
...
end
Or to skip authorize filter for all actions, use skip_before_filter :authenticate without the only option.
I'm using devise and trying the next following:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :is_worker
def is_worker
if user_signed_in?
#email = current_user.email
if #email && Worker.find_by_email(#email).nil?
redirect_to '/tasksadmins'
else
redirect_to '/workers'
end
else
redirect_to '/users/sign_in'
end
end
end
when I try to enter the site: localhost:3000/tasksadmins, I got:
Oops! It was not possible to show this website
The website at http://localhost:3000/tasksadmins seems to be unavailable. The precise error was:
Too many redirects
It could be temporarily switched off or moved to a new address. Don't forget to check that your internet connection is working correctly.
How can I fix it please?
before_filter is applied to every single request. That's why it's redirecting again and again.
You might want to only filter specific actions:
before_filter :is_worker, only: :index
Another solution would be to check wether a redirect is necessary in #is_worker:
redirect_to '/workers' unless request.fullpath == '/workers'
EDIT:
Another way would be to skip the before filter for the target actions of your redirects. Example:
class WorkersController < ApplicationController
skip_before_filter :is_worker, only: :index
# …
end
In my case:
users_controller.rb
before_action :logged_in?, only: :new
def new
#user = User.new
render layout: "session"
end
and
application_controller.rb
def logged_in?
redirect_to users_new_url unless current_user.present?
end
When I was trying to redirect to the 'users/new' page,same error occurred.
This is just because I'm trying to redirect to the 'users/new' page and "def logged_in?" is also redirecting to the same page.
Then I changed the application_controller.rb code like this:
def logged_in?
redirect_to root_url unless current_user.blank?
end
Error_Resolved.
Having problems figuring this out.
trying to do a
rescue_from NoMethodError, :with => :try_some_options
But its not working.
EDITED:
For testing I'm doing a simple redirect
def try_some_options
redirect_to root_url
end
EDITED 2:
Sample of my controller. Added (exception) as recommended below.
I know the reason I'm getting the error. Using Authlogic and authlogic_facebook_connect plugin. When user is created from the facebook plugin the "MyCar" model, which is associated with a user is not created like it normally is created if a user registers locally. Since I do call on the user model and reference the users car throughout different parts of the site, I would like to do something like what you see below and eventually put it in my application_controller.
class UsersController < ApplicationController
before_filter :login_required, :except => [:new, :create]
rescue_from NoMethodError, :with => :try_some_options
...
def show
store_target_location
#user = current_user
end
def create
#user = User.new(params[:user])
if #user.save
MyCar.create!(:user => #user)
flash[:notice] = "Successfully created profile."
redirect_to profile_path
else
render :action => 'new'
end
end
...
protected
def try_some_options(exception)
if logged_in? && current_user.my_car.blank?
MyCar.create!(:user => current_user)
redirect_to_target_or_default profile_path
end
end
...
end
EDITED 3: Hacked it for now since I know why the error is showing up, but would like to figure out how to rescue_from NoMethodError
class UsersController < ApplicationController
before_filter :login_required, :except => [:new, :create]
before_filter :add_car_if_missing
def add_car_if_missing
if logged_in? && current_user.my_car.blank?
MyCar.create!(:user => current_user)
end
end
end
I just read your post when trying to come up with a solution to the same problem. In the end I did the following:
class ExampleController < ApplicationController
rescue_from Exception, :with => :render_404
...
private
def render_404(exception = nil)
logger.info "Exception, redirecting: #{exception.message}" if exception
render(:action => :index)
end
end
This worked well for me. It is a catch all situation yet it just may help you. All the best.