I implemented authentication with Authlogic and authorization with Acl9. Now I'm trying to avoid multiple hits to database to check if user is admin by keeping this in the session.
What I thought is that this code should work:
class ApplicationController < ActionController::Base
...
helper_method :current_user_session, :current_user, :is_admin
...
private
def is_admin
return current_user_session[:is_admin] if defined?(current_user_session[:is_admin])
current_user_session[:is_admin] = current_user.has_role?(:admin)
end
So basically on a first call to is_admin helper method, it should add a boolean value to session[:is_admin] and then for any other calls, take it from the session. But I receive this error:
undefined method `[]=' for #<UserSession: {:unauthorized_record=>"<protected>"}>
And I stuck here. What am I doing wrong?
I had to use session[] instead of current_user_session[]. This code works as charm:
(ApplicationController)
helper_method :current_user_session, :current_user, :is_admin?
...
def is_admin?
return session[:is_admin] if !session[:is_admin].nil?
session[:is_admin] = current_user.has_role?(:admin)
end
(view template)
<% if is_admin? -%>
...
It will cache admin role in session on a first attempt and then will take it from there.
Related
I am looking for a way to check if Devise has any errors (invalid credentials etc.) for a before_action method in my ApplicationController. There is code in that method that I need only to run if Devise has no errors.
class ApplicationController < ActionController::Base
before_action :foo
def foo
if !devise_errors?
end
end
You can check credential errors like this:
class ApplicationController < ActionController::Base
before_action :foo
def foo
if !devise_errors?
end
..
private
def devise_errors?
login_params = devise_parameter_sanitizer.sanitize(:sign_in)
email = login_params.dig(:email)
password = login_params.dig(:password)
user = User.find_by(email: email)
return true if user.blank?
!user.valid_password?(password)
end
..
end
Do you mean sign in errors? Wouldn't you only need this in the session controller?
You could check the flash messages...
But you might be better off checking in Warden:
Warden::Manager.warden_callback do |user, auth, opts|
# code
end
I'm working on a login/logout system. Instead of using devise, I created an active records User model and use sessions to remember if a user is logged in. Everything was working fine until I added these lines in the application_controller.rb to have a layout before login and one after.
layout :set_layout
def set_layout
if session[:current_user_id]
'afterlogin'
else
'application'
end
end
Now, after I log in and cancancan is being used somewhere in a html page I get undefined local variable or method 'current_user'. I think that I have to add a current_user method but I'm not exactly where and how to define it.
Edit: I already had something similar in another class that is being used by login:
class Admin::ApplicationController < ApplicationController
before_action :authorize
def authorize
begin
#current_user ||= User.find(session[:current_user_id]) if session[:current_user_id]
rescue ActiveRecord::RecordNotFound
session.destroy
redirect_to '/login',alert: 'Please login'
end
end
end
Should I modify this after I add that method ?
CanCanCan expects a current_user method to exist in the controller.
First, set up some authentication (such as Authlogic or Devise).
See Changing Defaults if you need different behavior.
I would suggest you to install Devise so that it comes with a complimentary current_user method.
FYI: https://github.com/plataformatec/devise
UPDATE
when a user logins successfully, you can store the user's id in session.
session[:current_user_id]=user.id
so that, in your applicationcontroller, you can do
def current_user
#current_user ||= session[:current_user_id] && User.find_by_id(session[:current_user_id])
end
helper_method :current_user
I'm allowing my users to have multiple profiles (user has many profiles) and one of them is the default. In my users table I have a default_profile_id.
How do I create a "default_profile" like Devise's current_user which I can use everywhere?
Where should I put this line?
default_profile = Profile.find(current_user.default_profile_id)
Devise's current_user method looks like this:
def current_#{mapping}
#current_#{mapping} ||= warden.authenticate(:scope => :#{mapping})
end
As you can see, the #current_#{mapping} is being memoized. In your case you'd want to use something like this:
def default_profile
#default_profile ||= Profile.find(current_user.default_profile_id)
end
Regarding using it everywhere, I'm going to assume you want to use it both in your controllers and in your views. If that's the case you would declare it in your ApplicationController like so:
class ApplicationController < ActionController::Base
helper_method :default_profile
def default_profile
#default_profile ||= Profile.find(current_user.default_profile_id)
end
end
The helper_method will allow you to access this memoized default_profile in your views. Having this method in the ApplicationController allows you to call it from your other controllers.
You can put this code inside application controller by defining inside a method:
class ApplicationController < ActionController::Base
...
helper_method :default_profile
def default_profile
Profile.find(current_user.default_profile_id)
rescue
nil
end
...
end
And, can access it like current_user in your application. If you call default_profile, it will give you the profile record if available, otherwise nil.
I would add a method profile to user or define a has_one (preferred). Than it is just current_user.profile if you want the default profile:
has_many :profiles
has_one :profile # aka the default profile
I would not implement the shortcut method, but you want:
class ApplicationController < ActionController::Base
def default_profile
current_user.profile
end
helper_method :default_profile
end
I've been googling this one and haven't turned up anything useful:
Assuming you use http basic auth in Rails is there a simple method to check if the user is authenticated? Ie. a way you can do something like this in a view:
- if http_basic_authenticated?
# show admin menu
Try this:
class ApplicationController < ..
before_filter :authenticate
def authenticate
authenticate_or_request_with_http_basic do |username, password|
#authenticated = username == "foo" && password == "bar"
end
end
def authenticated?
#authenticated
end
helper_method :authenticated?
end
You can now use authenticated in your view.
Please write tests!
Use a session parameter accessible through a method defined in your ApplicationController.
class ApplicationController < BaseController
...
def authorize
session[:authorized] = true
end
def http_basic_authenticated?
session[:authorized]
end
def end_session
session[:authorized] = nil
end
end
P.S. I'm not a security expert, so I can't comment on the suitability of using this in a production environment.
From the oficial code you can extract snipets to use something like this in any controller inherited by ApplicationController:
class ApplicationController < BaseController
...
protected # Only inherited controllers can call authorized?
def authorized?
request.authorization.present? && (request.authorization.split(' ', 2).first == 'Basic')
end
...
end
well, as I could know, there's no way to tell a view that the request is not authenticated, you could just tell the view that it is authenticated, but why?
let's see the process of a request:
Client Request
Controller
View
and in the 2nd step, the particular controller's method, which is before-filtered by the authentication method, that is, if you can go to the 3rd step -- the view, the request must be authenticated.
I have a method that I want to execute some search logic, and then save a Search object that has the searched string and user id of the person who did the search.
The search/save logic seems to be working fine otherwise, but when I try to get the current user (using a method from the application controller) it throws a runtime error that has to do with the session:
ActionController::Metal#session delegated to #_request.session, but #_request is nil: #<SearchController:0x1038e32e0 #action_has_layout=true, #view_context_class=nil, #_status=200, #_headers={"Content-Type"=>"text/html"}>
Here's the method in the search controller:
class SearchController < ApplicationController
...
def factualsearch(search)
if search
searchquery = Search.new
# this causes the error
if current_user
searchquery.user = current_user
end
searchquery.search_string = search
searchquery.save
...
end
#results
end
end
Here's the current_user method I'm trying to call from my application controller:
def current_user
return unless session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
end
helper_method :current_user
Here's the pages controller where I'm calling the method:
class PagesController < ApplicationController
...
def search
searchcontrol = SearchController.new
#results = searchcontrol.factualsearch(params[:search])
end
...
end
Not exactly a direct way to fix this problem, but I was able to get around it by have the function accept the user in addition to the search query, rather than trying to call the current_user method from inside it. The class that calls the action can access the current_user method just fine.