0

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
Jeff
  • 4,285
  • 15
  • 63
  • 115
  • Figured out 'what' to do, but not how... I need to add a custom strategy to warden in an initializer. http://stackoverflow.com/questions/4223083/custom-authentication-strategy-for-devise – Jeff Jul 25 '15 at 14:08

3 Answers3

1

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
blnc
  • 4,384
  • 1
  • 28
  • 42
0

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."
Jeff
  • 4,285
  • 15
  • 63
  • 115
  • I think the test fails because the `devise\test_helpers.rb` function `sign_in` says: `# This method bypass any warden authentication callback.` – Jeff Jul 25 '15 at 16:57
0

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
Cyril Duchon-Doris
  • 12,964
  • 9
  • 77
  • 164