Leo
Leo

Reputation: 1514

Devise not authenticating with LDAP

I'm using Devise 3.5.2 for authentication with the devise_ldap_authenticable gem on Rails 4.2.4. I've moved from the released 0.8.5 gem to the github master level (0.8.6).

From what I can tell, the LDAP plugin authentication code is not running. It's not writing any log messages.

I previously got regular database authentication to work for this application. I'm now trying to get LDAP authentication to work.

For example:

Started POST "/users/sign_in" for ::1 at 2015-11-23 16:05:14 -0500
  ActiveRecord::SchemaMigration Load (87.6ms)  SELECT schema_migrations.* FROM schema_migrations
Processing by Devise::SessionsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"Mos...w==", "user"=>{"login"=>"leonsp", "password"=>"...", "remember_me"=>"0"}}
Completed 401 Unauthorized in 95ms (ActiveRecord: 0.0ms)

There should be LDAP log messages in there.

My user model:

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  #
  # :database_authenticable is not enabled
  devise :ldap_authenticatable, :registerable,
    :recoverable, :rememberable, :trackable, :validatable,
    :confirmable, :lockable, :zxcvbnable

  attr_accessible :email, :userid, :shortuserid, :login # etc...

  # Virtual attribute for authenticating by either userid or email
  # This is in addition to a real persisted field like 'userid'
  attr_accessor :login

  # ...

  def self.find_for_database_authentication(warden_conditions)
    Rails.logger.debug "Finding for database authentication"
    conditions = warden_conditions.dup
    login = conditions.delete(:login).try(:downcase)
    if login.present?
      Rails.logger.debug "Finding by login #{login}"
      where(conditions.to_hash).find_by([
        "lower(userid) = :value OR lower(shortuserid) = :value OR lower(email) = :value", { value: login }
      ])
    else
      Rails.logger.debug "Finding by conditions #{login}"
      find_by(conditions.to_hash)
    end
  end

  def self.find_for_ldap_authentication(warden_conditions)
    Rails.logger.debug "Finding for ldap authentication"
    conditions = warden_conditions.dup
    login = conditions.delete(:login).try(:downcase)
    if login.present?
      Rails.logger.debug "Finding by login #{login}"
      if login.include? "@"
        conditions[:email] = login
        super conditions
      else
        conditions[:userid] = login
        super conditions
      end
    else
      Rails.logger.debug "No login. Using default behaviour"
      super
    end
  end

  # ...

Here's the initializer for Devise. I added the monkeypatch at the front while debugging:

module Devise
  module Strategies
    class LdapAuthenticatable < Authenticatable
      def authenticate!
        Rails.logger.debug "=== Starting LDAP Authentication ==="
        super
        Rails.logger.debug "=== Done LDAP Authentication ==="
      end
    end
  end
end

# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
  # TODO: https://github.com/cschiewek/devise_ldap_authenticatable/issues/153

  # ==> LDAP Configuration
  # config.ldap_logger = true
  # config.ldap_create_user = false
  # config.ldap_update_password = true
  # config.ldap_config = "#{Rails.root}/config/ldap.yml"
  # config.ldap_check_group_membership = false
  # config.ldap_check_group_membership_without_admin = false
  # config.ldap_check_attributes = false
  # config.ldap_use_admin_to_bind = false
  # config.ldap_ad_group_check = false
  config.ldap_logger = true
  config.ldap_create_user = true
  config.ldap_update_password = true
  config.ldap_use_admin_to_bind = true

  config.ldap_auth_username_builder = proc do |attribute, login, ldap|
    username_string = "#{attribute}=#{login},#{ldap.base}"
    Rails.logger.debug "Generated username as #{username_string}"
    username_string
  end

  # ==> Configuration for any authentication mechanism
  # Configure which keys are used when authenticating a user. The default is
  # just :email. You can configure it to use [:username, :subdomain], so for
  # authenticating a user, both parameters are required. Remember that those
  # parameters are used only when authenticating and not when retrieving from
  # session. If you need permissions, you should implement that in a before filter.
  # You can also supply a hash where the value is a boolean determining whether
  # or not authentication should be aborted when the value is not present.
  config.authentication_keys = [:login, :email, :userid]

  # ...

I can successfully connect to the LDAP server on the machine using ldapsearch:

ldapsearch -x -W -D "cn=admin,dc=my_domain,dc=com" -H ldap://my_hostname.my_domain.com "(cn=leonsp)"

Here's the corresponding configuration the ldap.yml:

## Authorizations
# Uncomment out the merging for each environment that you'd like to include.
# You can also just copy and paste the tree (do not include the "authorizations") to each
# environment if you need something different per environment.
authorizations: &AUTHORIZATIONS
  allow_unauthenticated_bind: false
  group_base: ou=groups,dc=my_domain,dc=com
  ## Requires config.ldap_check_group_membership in devise.rb be true
  # Can have multiple values, must match all to be authorized
  required_groups:
  ## Requires config.ldap_check_attributes in devise.rb to be true
  ## Can have multiple attributes and values, must match all to be authorized
  require_attribute:
    objectClass: inetOrgPerson

## Environment

development:
  host: ldap://my_hostname.my_domain.com
  port: 389
  attribute: cn
  base: dc=my_domain,dc=com
  admin_user: cn=admin,dc=my_domain,dc=com
  admin_password: ...
  ssl: none
  # <<: *AUTHORIZATIONS

Am I correct in assuming that the LDAP plugin code for Devise is not running?

Why isn't it running?

Why aren't any of my debug statements being reached?

What additional methods can I monkeypatch with debug statements to debug the issue?

What other diagnostics can I collect?

Edit: I'm tracing the execution using the byebug debugger. So far I can tell that :ldap_authenticable shows up on the list of strategies during sign in, but doesn't seem to result in any :ldap_authenticable-specific code execution.

Upvotes: 3

Views: 1329

Answers (2)

Ed W.
Ed W.

Reputation: 3

For those coming to this thread still, I was able to get it to work by dropping down to gem version 0.8.4.

Upvotes: 0

Leo
Leo

Reputation: 1514

The main issue was that I had included optional parameters in config.authentication_keys. This made Devise::Strategies#parse_authentication_key_values fail silently. Only mandatory parameters should be included in authentication_keys.

Other issues related to the application being designed to allow users to log in with either their username or their email address. To allow for users to log in using either, I had to:

  • Override login_with in the User model
  • Override self.find_for_ldap_authentication in the User model
  • Rename the fake attr_accessor :login to attr_accessor :username in order to better distinguish it from the different meaning of :login in the gem

One last issue: Don't include protocol (e.g. ldaps://) in the host attributes of ldap.yml

Upvotes: 3

Related Questions