I've got a situation where User after successfully Sign up process (I'm using Devise gem) will have access only for two controllers until he passes all validation process. To check if validation process is in progress I'm using status column. If status == '' means validation is not finished.
I'm wondering how to implement such an access to only two pages? my first thought was Pundit gem but if I have 50 controllers I would need to implement 50 policies, inside of which I'll have numerous methods correspondent to controller actions. Is there any better way to do so?
[EDIT]
This is what I have so far:
class BaseController < ApplicationController
before_action :authorized_user
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def authorized_user
policy_scope(current_user)
end
private
def user_not_authorized
flash[:alert] = 'You are not authorized to perform this action.'
redirect_to(request.referrer || root_path)
end
end
User policy:
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
return raise(Pundit::NotAuthorizedError) unless user.status == 'active'
scope.all
end
end
end
This Answer only works if your controllers inherit from ApplicationController.
Add a before_action to your ApplicationController which does something when the user is not validated. This triggers now on all controller actions.
class ApplicationController < ActionController::Base
before_action :require_validated
private
def require_validated
redirect_to somewhere_url unless current_user.status == 'validated'
end
end
To except specific controller you can skip_before_action
class SomeController < ApplicationController
skip_before_action :require_validated
# skip only for specific actions
skip_before_action :require_validated, only: [:new, :create]
end
Thanks to the suggestions in the comments from #tadam I ended up with below code:
#intermediate controller class
class BaseController < ApplicationController
before_action :authorized_user
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def authorized_user
authorize :global, :only_active?
end
private
def user_not_authorized
flash[:alert] = 'You are not authorized to perform this action.'
redirect_to root_path
end
end
Global policy used in BaseController
class GlobalPolicy < ApplicationPolicy
def only_active?
active?
end
end
Related
I am using the idiom described in https://guides.rubyonrails.org/v2.3/action_controller_overview.html#other-ways-to-use-filters
# /app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter do |controller|
redirect_to new_login_url unless controller.send(:logged_in?)
end
end
Now if the signing in process is successful, how
can I examine if it was, and b)
how can I re-redirect the user to the requested controller action?
How do I do this login-process via AJAX and JSON ?
EDIT: Also I get the following Error Message
uninitialized constant ApplicationController::LoginFilter
When I use the more elaborate solution suggested in 6.2 Other Ways to Use Filters instead of the one above such that my Controller looks like this
# /app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_return_path, LoginFilter
def set_return_path
return if devise_controller?
session['user_return_to'] = request.url unless current_user
end
class LoginFilter
def self.filter(controller)
unless controller.send(:logged_in?)
controller.flash[:error] = "You must be logged in"
controller.redirect_to controller.new_login_url
end
end
end
end
Thanks
von Spotz
You can add a before_action in the application_controller.rb where you save the requested page url:
class ApplicationController < ActionController::Base
before_action :set_return_path
def set_return_path
return if devise_controller?
session['user_return_to'] = request.url unless current_user
end
end
And then redirect the user to this url after the successful sign-in:
class SessionsController < Devise::SessionsController
def after_sign_in_path_for(resource)
return root_url if session['user_return_to'].blank?
session['user_return_to']
end
end
I want to use the before_action in application_controller.rb and then some skip_before_actions to prevent some sites from being called before a user is logged in.
But the defined function in my application_controller.erb is not called ...
application_controller.erb
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
layout "application"
before_action :user_logged_in, :set_locale
private
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
# Prüft ob ein Nutzer eingeloggt ist.
def user_logged_in
puts "HA"
if session[:user_id].nil?
flash[:error] = "error"
redirect_to :controller => :startsites, :action => :index
else
flash[:error] = "ok"
redirect_to :controller => :startsites, :action => :index
end
end
end
The puts "HA" in user_logged_in is not printed in my server console. So I think the function is not called yet .. but why?
And in some controllers I tried to use this:
class MoviesController < ActionController::Base
skip_before_action :user_logged_in, only: [:index, :show]
also not working ... why?
Thank you very much for your help.
You are trying to call through ActionController. Its not possible that way, as you built it.
ActionController::Base
-ApplicationController #your method in this controller
ActionController::Base
-MoviesController #yr trying to skip it right here
To skip it, you have to inherit like below:
ActionController::Base
-ApplicationController #yr method is here
--MoviesController #it will find that method and skip it.
Controllers
# application_controller.rb
class ApplicationController < ActionController::Base
end
# movies_controller.rb
class MoviesController < ApplicationController
end
I have two layouts Admin and Domain. And I don't need any extra configuration in Admin layout. but if user tries to access Domain layout they must be in their valid domain.
This means that, I need to customize all of my Domain policy to include both current_user as well as current_domain. I found this can be done with UserContext and pundit_user... so here is what I have done:
application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def pundit_user
UserContext.new(current_user, current_domain)
end
def after_sign_out_path_for(resource)
root_path
end
def current_domain
#current_domain ||= Domain.where(name: requested_domain).first
end
helper_method :current_domain
private
def requested_domain
return request.env["SERVER_NAME"]
end
def user_not_authorized
# reset_session
flash[:alert] = "You are not authorized to perform this action"
redirect_to(request.referrer || root_path)
end
end
Note that, when I access Admin layout, current_domain will be nil and if I visit any routes of Domain layout, then current_domain will set to currently accessing domain.
user_context.rb
class UserContext
attr_reader :current_user, :current_domain
def initialize(current_user, current_domain)
#current_user = current_user
#current_domain = current_domain
end
end
PROBLEM
Suppose I have this policy:
user_policy.rb
class UserPolicy < ApplicationPolicy
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def index?
binding.pry # debugging
current_user.admin? ||
current_user.domain == current_domain
end
private
def current_user
# return user.is_a?(User) ? user : user.current_user
user.current_user
end
def current_domain
# return user.is_a?(User) ? nil : user.current_domain
user.current_domain
end
end
when application runs current_user and current_domain must available in UserPolicy as per documentation(https://github.com/elabs/pundit#additional-context).
But I am getting
undefined method `current_user' for #<User:0x007fcefbc2b150>
That means, still I have user object in it, not user.current_user and user.current_domain
Please let me know, if you need further description. What am I missing here?
It was my own dumb mistake.
PROBLEM
I had a before_filter call in domain/base_controller.rb something like:
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# PROBLEM: this line was updating pundit_user again to user object
raise Pundit::NotAuthorizedError unless DomainConsolePolicy.new(current_user, current_domain).authorized?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
SOLUTION:
I have used headless policy for this because now I have both current_user and current_domain set with pundit_user in application_controller
domain/base_controller.rb
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# SOLUTION
authorize :domain_console, :has_access?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
policy/domain_console_policy.rb
class DomainConsolePolicy < Struct.new(:user, :domain_console)
def has_access?
user.current_user.admin? ||
user.current_user.domain_id == user.current_domain.id
end
end
Thanks
I want to execute some function before any controller action to check if the user is signed in. I am using devise so that I can use is_signed_in?, but I have to put if else condition to every method in the controller.
What I want is to have something like this:
#some_controller.rb
before_action :is_signed_in?
def is_signed_in?
if !user_signed_in?
redirect_to new_user_session_path
else
..proceed to the action intended to call
end
end
So I want this method to execute before any action (or some set of actions) and redirect to sign in if false or let that action to be executed if true.
Devise is shipped with some useful built-in helpers.
In your case, one that interested you is authenticate_user!. Take a look at controller filters and helpers in Devise documentation.
You can filter your actions in your controller with this method to ensure only logged-in users can process a given action or all actions in a controller, else if user isn't logged-in then he is redirect to login page.
before_action :authenticate_user!
before_action :authenticate_user!, only: [:show]
You can also create your own helper method.
In your users_controller.rb, create a before_action filter
class UsersController < ApplicationController
before_action :logged_in_user
...
end
and in your session_helper.rb
module SessionHelper
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to login_url
end
end
end
If you want to check whether user is signed for every action in the application, you have to put the filter in the application controller. You can do this for a specific controller also.
You can use the devise method:
class SomeController < ApplicationController
before_action :authenticate_user!
...
end
You can create your own filter also:
class SomeController < ApplicationController
before_action :my_authentication
...
def my_authentication
if user_signed_in?
# do something ...
else
# do something else ...
end
end
end
Are you using devise? You can use existing filter:
class SomeController < ApplicationController
before_filter :authenticate_user!
...
end
if not, create your filter in application controller and add it to your needed controllers:
class SomeController < ApplicationController
before_filter :my_auth_filter
...
end
You can add this method to your ApplicationController
def user_is_logged_in
if !session[:current_user]
redirect_to login_path
end
end
Use it before invoking any actions. Like so,
class AdminsController < ApplicationController
before_action :user_is_logged_in
...
end
I have a controller and every method of it starts with the following code:
#user = UserData.find_by_login(session[:cuser])
if #user == nil
redirect_to(:controller=> 'user_data', :action=> 'login')
return
end
I'm just wondering if it is possible to avoid code duplication in this case ?
Yes, use a before_filter
class YourController < ApplicationController
before_filter :check_user
def check_user
..
end
end
Absolutely.
class MyController < ApplicationController
before_filter :ensure_logged_in
# actions here.
def ensure_logged_in
#user = UserData.find_by_login(session[:cuser])
if #user == nil
redirect_to(:controller=> 'user_data', :action=> 'login')
end
end
end
You shouldn't need to worry about the 'return', as rails will bail out of the filter pipeline once the redirect happens.
To avoid duplication you just need to add before_filter in every controller where you want to check user authentication.
class SomeController < ApplicationController
before_filter :authenticate_user
end
then add your user authentication logic in application controller something like this,
class ApplicationController < ActionController::Base
private
def current_user
#current_user ||= UserData.find_by_login(session[:cuser]) if session[:cuser]
end
helper_method :current_user
def authenticate_user
redirect_to({:controller=> 'user_data', :action=> 'login'}, :alert => "Not authorized") if current_user.nil?
end
end
You can use current_user helper method in every controller to get current user.
Try to use before filter. This should be fine