Authlogic, Namespace and private methods in ApplicationController - ruby-on-rails

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

Related

Define a generic method like current_user rails

I would like to create a generic method like the current_user method provided by Devise which is usable on views and controllers. In my case, I want to know the current company.
My code :
class ApplicationController < ActionController::Base
before_action :set_company
protected
def set_company
#current_company =|| nil
if current_user.admin? && session[:company_id].present?
#current_company =|| Company.find(session[:company_id])
else
#current_company =|| current_user.company
end
end
end
What you've got almost does it... just add the method itself and make it a helper method so it's available in views.
class ApplicationController < ActionController::Base
helper_method :current_company
before_action :set_company
def current_company
#current_company
end
protected
def set_company
#current_company =|| nil
if current_user.admin? && session[:company_id].present?
#current_company =|| Company.find(session[:company_id])
else
#current_company =|| current_user.company
end
end
end
add the helper_method property,
and make the method public
helper_method :set_company
this will make it available in every view in your application

How to make #current_user available in my views?

In my application controller I have a module that I include like:
require 'current_user'
class ApplicationController < ActionController::Base
include CurrentUser
end
So if there is a token cookie set, it looks up the user.
In my views i am doing this:
<% if #current_user.present? %>
...
..
This doesn't work currently in my views because there is no #current_user or current_user in my views.
How can I create a #current_user variable globally for all my view pages to access?
I'm not sure what your module does, but you can just make a method on in ApplicationController:
def current_user
# ... fetch current_user somehow...via your module method, or session:
#current_user ||= session[:user_id] ? User.find(session[:user_id]) : nil
end
Then you can access it in your views via:
<% if current_user.present? %>
If you really want to force it to be #current_user, I guess the easiest way would be to add it as a before_action:
class ApplicationController < ActionController::Base
before_action :set_current_user
private
def set_current_user
#current_user = session[:user_id] ? User.find(session[:user_id]) : nil
end
end
Now you can access it in views via #current_user, but it will be eagerly fetched even if you aren't using it in your views. Using first method will only query for current user if you use it.
In my module I just some ruby metaprogramming to include it in my application_controller class:
module CurrentUser
def self.included(base)
base.send :helper_method, :current_user
end
end
Now current_user works in my views.

pundit_user: undefined method `current_user' for #<User:0x007fcefbc2b150>

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

Code in each controller method

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

Get value from session on every method of every controller

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

Resources