I'm using Authlogic and I would like to implement Basic HTTP Authentication in my controller so I could define which action requires authentication.
I know how to do Basic HTTP Authentication authenticate_or_request_with_http_basic an before_filter, but I would like to here from other how to implement it with Authlogic plugin.
class ItemsController < ApplicationController
before_filter :authenticate , :only => [:index, :create]
...
end
I've had success with the following:
Define a filter method in application_controller.rb
def require_http_auth_user
authenticate_or_request_with_http_basic do |username, password|
if user = User.find_by_login(username)
user.valid_password?(password)
else
false
end
end
end
Then in your controller you can do the following:
before_filter : require_http_auth_user
You can then use:
http://username:password#yoursite.com (i.e. http basic authentication)
Hope this helps.
Here is a great screencast that explains, step-by-step, how to use authlogic in your rails project.
Once authlogic is set up, define the following useful authentication-related helper methods in your Application Controller.
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.record
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
def require_no_user
if current_user
store_location
flash[:notice] = "You must be logged out to access this page"
redirect_to root_url
return false
end
end
Once those methods are defined, you can specify actions that require the user to be logged in:
before_filter :require_user, :only => [:new, :edit]
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
How do you override the Devise controller to only allow 'admins' to log in?
This is what I came up with:
class SessionsController < Devise::SessionsController
def create
if current_user.admin?
# tell the user "you can't do that"
else
super
end
end
end
but the user was able to log in (probably because 'current_admin' is not defined yet?). Here is the original devise controller action:
class Devise::SessionsController < DeviseController
prepend_before_filter :require_no_authentication, only: [:new, :create]
prepend_before_filter :allow_params_authentication!, only: :create
prepend_before_filter :verify_signed_out_user, only: :destroy
prepend_before_filter only: [:create, :destroy] { request.env["devise.skip_timeout"] = true }
...
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
...
end
Edit: I don't think I should change the session controller, I think I should add a strategy to Warden. I tried this and it still logs in non admin users:
config/initializers/custom_warden_strategies.rb:
Warden::Strategies.add(:admin_only) do
def authenticate!
resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
encrypted = false
if validate(resource) { encrypted = true; resource.valid_password?(password) }
if resource.admin?
remember_me(resource)
resource.after_database_authentication
success!(resource)
end
end
mapping.to.new.password = password if !encrypted && Devise.paranoid
fail(:not_found_in_database) unless resource
end
end
config\initializers\devise.rb
config.warden do |manager|
manager.default_strategies.unshift :admin_only
end
Give this a try:
class SessionsController < Devise::SessionsController
def create
super do
if !resource.admin?
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message :notice, :signed_out if signed_out && is_flashing_format?
respond_to_on_destroy
end
end
end
end
I found a solution, but it fails with my test suite (works when I manually test it though).
config/initializers/admin_only_initializer.rb
require 'devise/strategies/authenticatable'
module Devise
module Strategies
# Default strategy for signing in a user, based on their email and password in the database.
class AdminOnly < Authenticatable
def authenticate!
resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
encrypted = false
if validate(resource){ encrypted = true; resource.valid_password?(password) }
if resource.admin?
success!(resource)
else
fail!(:not_permitted)
end
end
mapping.to.new.password = password if !encrypted && Devise.paranoid
fail(:not_found_in_database) unless resource
end
end
end
end
Warden::Strategies.add(:admin_only, Devise::Strategies::AdminOnly)
config/initializers/devise.rb
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :admin_only
end
and the I18n string (config/locales/devise.en.yml):
en:
devise:
failure:
not_permitted: "You are not permitted to complete this action."
Why prevent non-admins from logging in and not just block some actions for non-admin users ? How do you make the difference between an admin and a simple user then ?
Remember, the one true security principle is DENY then ALLOW. So to make sure you application remains safe when you keep adding stuff (AGILE development for example), I suggest the following approach
application_controller.rb
class ApplicationController
before_action :authenticate_user!
# Security policy deny then access
before_filter :access_denied
# Actually I have refactorised below code in a separate security.rb module that I include in ApplicationController, but as you wish
def access_denied
if #denied and not #authorized
flash[:alert] = 'Unauthorized access'
flash[:info] = "Authorized entities : #{#authorized_entities.join(', ')}" if #authorized_entities
flash[:warning] = "Restricted to entities : #{#restricted_entities.join(', ')}" if #restricted_entities
render 'static_pages/home', :status => :unauthorized and return
false
end
end
def allow_access_to_administrators
(#authorized_entities ||= []) << "Administrators"
#authorized = true if administrateur_logged_in?
end
def administrateur_signed_in?
user_signed_in? and current_user.administrator? # Or whatever method you use to authenticate admins
end
end
Note that I use both #authorized and #denied.
I use #authorized generally for a class of users (like admins), whereas I set #denied if, for a class of users, I want to restrict to a subset.
Then I use
your_controller_reserved_for_admins.rb
prepend_before_filter :allow_access_to_administrators
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 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 the standard current_user methods in my application_controller.
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.record
end
In my UserSessionController create method, I checked for
registration_complete? (simply a check on a value of a column, which
works fine) and then redirect the user to the user edit page if the
registration is not complete. When I debug this, I can see that the
attempted_record for the #user_session object exists and it's pointing
to the correct user, but the current_user method in
the redirect_to edit_user_path(current_user),
always returns nil.
What's going wrong here?
def create
#user_session = UserSession.new(params[:user_session])
# uses a block to prevent double render error...
# because oauth and openid use redirects
#user_session.save do |result|
if result
flash[:notice] = "Login successful!"
if #user_session.new_registration?
logger.info "Session controller : new
registration"
logger.info "Complete -
#{#user_session.registration_complete?}"
flash[:notice] = "Please review profile"
redirect_to edit_user_path(current_user)
else
if #user_session.registration_complete?
logger.info "Session Controller - registration
complete"
else
flash[:notice] = "Please complete profile"
logger.info "Session Controller - registration
not complete"
redirect_to edit_user_path(current_user)
#current_user nil here
end
end
redirect_to current_user ? profile_url(current_user) :
login_url
else
if #user_session.errors.on(:user)
# if we set error on the base object, likely it's
because we didn't find a user
render :action => :confirm
else
render :action => :new
end
end
end
end
I have seen this and used that very same authentication system on one of my applications. You don't mention it and that's why I'm proposing this to you if you do have then I'd need to see more of your application_controller, so anyway try and add this before the methods.
helper_method :current_user
I just had a duh moment, try doing this instead of passing a block to the save method.
if #user_session.save
# rest of your logic here
end
"if result" might not do what you expect it to do.
Else inspect this #user_session.record instead of current_user, if it's still nul your problem is not current_user.
I need to get home, I'll check back later tonight.
I had similar issue with authlogic and rails 3, and the problem that i figured out was
the apache http authentication that was enabled in my production server,
here is the solution to get that fixed, check Rails + Authlogic #current_user problem - "Show" method no longer works
Try to use this to rails2:
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery # See ActionController::RequestForgeryProtection for details
#Authlogic
filter_parameter_logging :password
helper_method :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.record
end
end
and this to rails3:
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery # See ActionController::RequestForgeryProtection for details
helper_method :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.record
end
end
/config/application.rb
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]