class InclineLdap::AuthEngine

Defines an engine used to authenticate a user against an LDAP provider.

Constants

BindError

Raised when the configuration looks good but we are still unable to connect.

ConnectionError

An error raised when attempting to establish a connection to the LDAP provider.

InvalidConfiguration

Raised when a configuration value is invalid.

Public Class Methods

new(options = {}) click to toggle source

Creates a new LDAP authentication engine.

Valid options:

host

The LDAP host name or IP address. (required)

port

The port to connect to (defaults to 389 for non-ssl and 636 for ssl).

ssl

Should SSL be used for the connection (recommended, default is true).

base_dn

The base DN to search within when looking for user accounts. (required)

browse_user

A user to log in as when looking for user accounts. (required)

browse_password

The password for the browse_user.

email_attribute

The attribute to use when looking for user accounts (default is 'mail').

auto_create

If true, users are automatically created when they successfully authenticate against the LDAP provider for the first time.

auto_activate

If this and auto_create are both true, then newly created users will be activated. If auto_create is false, this option has no effect. If this is false and auto_create is true, newly created users will need to activate their accounts as if they had signed up.

# File lib/incline_ldap/auth_engine.rb, line 49
def initialize(options = {})
  @options = {
      ssl: true,
      email_attribute: 'mail'
  }.merge(options || {})

  @options[:port] = @options[:port].to_s.to_i unless @options[:port].is_a?(::Integer)

  if @options[:port] == 0
    @options[:port] = (@options[:ssl] ? 636 : 389)
  end

  raise InvalidConfiguration, "Missing value for 'host' parameter." if @options[:host].blank?
  raise InvalidConfiguration, "The value for 'port' must be between 1 and 65535." unless (1..65535).include?(@options[:port])
  raise InvalidConfiguration, "Missing value for 'base_dn' parameter." if @options[:base_dn].blank?
  raise InvalidConfiguration, "Missing value for 'email_attribute' parameter." if @options[:email_attribute].blank?
  raise InvalidConfiguration, "Missing value for 'browse_user' parameter." if @options[:browse_user].blank?

  ldap_opt = {
      host: @options[:host],
      port: @options[:port],
      base: @options[:base_dn],
      auth: {
          method: :simple,
          username: @options[:browse_user],
          password: @options[:browse_password]
      }
  }

  if @options[:ssl]
    @options[:ssl] = @options[:ssl].to_sym if @options[:ssl].is_a?(::String)

    unless [:simple_tls, :start_tls].include?(@options[:ssl])
      @options[:ssl] =
          if @options[:port] == 389
            :start_tls
          else
            :simple_tls
          end
    end

    ldap_opt[:encryption] = { method: @options[:ssl] }
  end
  
  ::Incline::Log::debug "Creating new LDAP connection to #{@options[:host]}:#{@options[:port]}..."
  @ldap = Net::LDAP.new(ldap_opt)
  
  ::Incline::Log::debug 'Binding to LDAP server...'
  raise BindError, "Failed to connect to #{@options[:host]}:#{@options[:port]}." unless @ldap.bind

  ::Incline::Log::info "Connected to LDAP host #{@options[:host]}:#{@options[:port]}."
end

Public Instance Methods

authenticate(email, password, client_ip) click to toggle source

Authenticates a user against an LDAP provider.

# File lib/incline_ldap/auth_engine.rb, line 134
def authenticate(email, password, client_ip)
  ldap_filter = "(&(objectClass=person)(#{@options[:email_attribute]}=#{email}))"

  reset_ldap!

  search_result = @ldap.search(filter: ldap_filter)

  if search_result && search_result.count == 1
    search_result = search_result.first
    
    user = ::Incline::User.find_by(email: email)
    
    if @options[:auto_create] && user.nil?
      rpwd = ::SecureRandom.urlsafe_base64(48)
      ::Incline::Recaptcha::pause_for do
        user =
            ::Incline::User.create(
                email: email,
                password: rpwd,
                password_confirmation: rpwd,
                name: search_result[:displayName]&.first || search_result[:name]&.first || search_result[:cn]&.first,
                recaptcha: 'none',
                enabled: true,
                activated: !!@options[:auto_activate],
                activated_at: (@options[:auto_activate] ? Time.now : nil)
            )
        unless @options[:auto_activate]
          user.send_activation_email client_ip
        end
      end
    end
    
    if user
      unless user.enabled?
        add_failure_to user, '(LDAP) account disabled', client_ip
        return nil
      end
      # blank passwords are always invalid.
      entry = password.to_s.strip == '' ? nil : @ldap.bind_as(filter: ldap_filter, password: password)
      if entry && entry.count == 1
        add_success_to user, '(LDAP)', client_ip
        return user
      else
        add_failure_to user, '(LDAP) invalid password', client_ip
        return nil
      end
    end
  end
  add_failure_to email, '(LDAP) invalid email', client_ip
  nil
end
base_dn() click to toggle source

Gets the Base DN for this LDAP authenticator.

# File lib/incline_ldap/auth_engine.rb, line 122
def base_dn
  @options[:base_dn]
end
email_attribute() click to toggle source

Gets the email attribute name for this LDAP authenticator.

# File lib/incline_ldap/auth_engine.rb, line 128
def email_attribute
  @options[:email_attribute]
end
host() click to toggle source

Gets the host name or IP address for this LDAP authenticator.

# File lib/incline_ldap/auth_engine.rb, line 104
def host
  @options[:host]
end
port() click to toggle source

Gets the host port for this LDAP authenticator.

# File lib/incline_ldap/auth_engine.rb, line 110
def port
  @options[:port]
end
ssl() click to toggle source

Gets the SSL method for this LDAP authenticator.

# File lib/incline_ldap/auth_engine.rb, line 116
def ssl
  @options[:ssl]
end

Private Instance Methods

reset_ldap!() click to toggle source
# File lib/incline_ldap/auth_engine.rb, line 188
def reset_ldap!
  @ldap.auth @options[:browse_user], @options[:browse_password]
  @ldap.bind
end