I'm beginning with Rails for some days now. I'm trying to make a form application that requires the users to be logged in in every case.
So I made the user login Railcast : http://railscasts.com/episodes/250-authentication-from-scratch
Now, I need to make the login required in my other controllers, so the user can't access the whole application without being logged in.
I tryed this method :
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def logged_in
return true if current_user
end
def login_required
if logged_in false
redirect_to log_in_path and return false
end
end
end
categories_controller.rb
class CategoriesController < ApplicationController
before_filter :login_required
def new
def index
#categories = Categorie.all
end
It returns me this error :
ArgumentError in CategoriesController#index
wrong number of arguments (1 for 0)
Extracted source (around line #14):
def logged_in
return true if current_user
end
Does my before_filter :login_required needs something else ?
I don't really understand this error.
You defined a method named logged_in which take no arguments but you are calling it with 1 argument:
if logged_in(false)
You should be doing:
if logged_in
Your code should probably look like this:
def logged_in
current_user
end
def login_required
return false if logged_in
redirect_to log_in_path
end
Related
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'm trying to get before_filter to work on the actions that requires the user to be logged in, however something must be wrong because it's not.
I use a helper file called 'session_helper.rb' for login/logout as well as for checking if the user is logged in (signed_in?). That works fine if used inside an action or in the view, however while using it with the before_filer it's not working. If I log out the user and try to access '/projects/new' it's possible to do that, while it shouldn't be.
What am I doing wrong?
project controller:
class ProjectsController < ApplicationController
before_filter :signed_in?, :except => [:index] // <-- doesn't prevent e.g. the action "new" to be executed
def new
#project = Project.new
#users = (current_user.blank? ? User.all : User.find(:all, :conditions => ["id != ?", current_user.id]))
end
def index
#projects = Project.all
if signed_in? // <-- works as it should
#users_projects = Project.where(:user_id => current_user.id)
end
end
... other actions ...
end
sessions_helper.rb
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
end
So, before_filter is a slightly misleading name. It is not really a filter. It isn't that it'll filter out the other actions and prevent them occurring if you return a falsey value, and allow them if you return a truthy one. It's really a way of calling a method before anything else. Think of it as 'before calling the action that the route has triggered, call the following method'.
Indeed, in Rails 4 they are renaming before_filter to before_action and that should alleviate the confusion moving forward.
You're just returning T/F from signed_in? So it's checking that and moving on, as you haven't told it to do anything special based on the results of that check.
So rather than calling signed_in? Something like this would work:
before_filter :authorize, :except => [:index]
def authorize
redirect_to login_url, alert: "Not authorized" if !signed_in?
end
Hop that helps.
I've always seen the before_filter raise an exception or redirect to another page when there is no current login. I am not sure that returning false will prevent the page from rendering.
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
I have some controller and in each method of each controller I have next code:
#user = session[:user]
Is there a way to avoid putting this code on every method of each controller?
You can add your code in ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :current_user
def current_user
#user = session[:user]
end
end
The anwer of #nash is fine, and here is an alternative providing helper methods you can use in every method/view. This is the way gems like Devise go:
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
helper_method :user_signed_in?
private
def current_user
#current_user ||= User.find_by_id(session[:user_id]) if session[:user_id]
end
def user_signed_in?
return 1 if current_user
end
def authenticate_user!
if !current_user
flash[:error] = 'You need to sign in before accessing this page!'
redirect_to signin_services_path
end
end
end
I am troubleshooting why my ApplicationController's methods don't seem to work in my namespaced admin area and it seems like when I'm in a namespace I cannot access my ApplicationController's private methods, is this right?
If it is like that, what's the best practise to reuse something like Authlogic's example ApplicationController methods in my namespaced controller? I could easily copy and paste the methods to an AdminController or something, and I could also un-private these methods - but this doesn't seem like such a good way to do it.
Here's what the example ApplicationController from Authlogic (and mine) looks like:
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user_session, :current_user
private
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.user
end
def require_user
unless current_user
store_location
flash[:notice] = "You must be logged in to access this page"
redirect_to new_user_session_url
return false
end
end
# and some more methods here...
end
And that's how I inherit from it in my namespace:
class Admin::DashboardController < ApplicationController
layout 'administration'
require_user # fails
def index
end
end
Thanks for your help,
Arne
You should use before_filter in Admin::DashboardController:
class Admin::DashboardController < ApplicationController
layout 'administration'
before_filter :require_user
def index
end
end
I haven't used authlogic, but maybe you need to change
require_user
to
before_filter :require_user