class LDAPConnection

Attributes

base[RW]
groups[RW]
host[RW]
logger[RW]
port[RW]

Public Class Methods

new(host, port, base, groups, logger) click to toggle source
# File lib/ons-ldap/ldap_connection.rb, line 16
def initialize(host, port, base, groups, logger)
  self.class.host = host
  self.class.port = port.to_i
  self.class.base = base
  self.class.groups = groups
  self.class.logger = logger
end

Public Instance Methods

authenticate(username, password) click to toggle source
# File lib/ons-ldap/ldap_connection.rb, line 24
def authenticate(username, password)
  user_entry = nil

  # Have to use the username DN format below for the bind operation to succeed.
  auth = { method: :simple, username: "uid=#{username},ou=Users,#{self.class.base}", password: password }

  Net::LDAP.open(host: self.class.host, port: self.class.port, encryption: :simple_tls, base: self.class.base, auth: auth) do |ldap|
    unless ldap.bind
      result = ldap.get_operation_result
      self.class.logger.error "LDAP authentication failed for '#{username}': #{result.message} (#{result.code})"
      return nil
    end

    self.class.logger.info "LDAP authentication succeeded for '#{username}'"
    user_entry = entry_for(username, ldap) || nil

    # The user must be a member of at least the "<zone>-users" group for authentication to be considered successful.
    users_group = self.class.groups['users']
    unless group_member?(users_group, username, ldap)
      self.class.logger.error "LDAP authentication failed: '#{username}' is not a member of the '#{users_group}' group"
      return nil
    end

    user_entry.groups = groups_for(username, ldap)
  end

  user_entry
end

Private Instance Methods

entry_for(username, ldap) click to toggle source

Returns the LDAP directory entry for a user.

# File lib/ons-ldap/ldap_connection.rb, line 56
def entry_for(username, ldap)
  filter     = Net::LDAP::Filter.construct("uid=#{username}")
  attributes = %w(uid displayName employeeNumber) # employeeNumber is used to store the 2FA token.
  user_entry = nil

  succeeded = ldap.search(filter: filter, attributes: attributes, return_result: false) do |entry|
    user_entry = UserEntry.new(entry.uid.first, entry.displayName.first, entry.employeeNumber.first)
  end

  unless succeeded
    result = ldap.get_operation_result
    self.class.logger.error "Error searching the LDAP directory using filter '#{filter}': #{result.message} (#{result.code})"
  end

  user_entry
end
group_member?(group, username, ldap) click to toggle source

Returns whether a user is a member of a group.

# File lib/ons-ldap/ldap_connection.rb, line 89
def group_member?(group, username, ldap)
  self.class.logger.info "group: '#{group}'"
  filter     = Net::LDAP::Filter.construct("(&(cn=#{group})(memberUid=#{username}))")
  attributes = %w(cn)
  group_cn   = nil

  succeeded = ldap.search(filter: filter, attributes: attributes, return_result: false) do |entry|
    group_cn = entry.cn.first
  end

  unless succeeded
    result = ldap.get_operation_result
    self.class.logger.error "Error searching the LDAP directory using filter '#{filter}': #{result.message} (#{result.code})"
  end

  group_cn == group
end
groups_for(username, ldap) click to toggle source

Returns the LDAP groups a user belongs to.

# File lib/ons-ldap/ldap_connection.rb, line 74
def groups_for(username, ldap)
  filter     = Net::LDAP::Filter.construct("(&(objectClass=posixGroup)(memberUid=#{username}))")
  attributes = %w(cn)
  groups     = []
  succeeded = ldap.search(filter: filter, attributes: attributes, return_result: false) do |entry|
    groups << entry[:cn].first
  end
  unless succeeded
    result = ldap.get_operation_result
    self.class.logger.error "Error searching the LDAP directory using filter '#{filter}': #{result.message} (#{result.code})"
  end
  groups
end