class GitHub::Ldap

Constants

ACTIVE_DIRECTORY_V51_OID

Internal: The capability required to use ActiveDirectory features. See: msdn.microsoft.com/en-us/library/cc223359.aspx.

DEFAULT_FIXTURES_PATH

Preconfigured user fixtures. If you want to use them for your own tests.

DEFAULT_SERVER_OPTIONS

Attributes

ldap_server[R]

ldap_server: is the instance of the testing ldap server,

you should never interact with it,
but it's used to grecefully stop it after your tests finalize.
server_options[R]

server_options: is the options used to start the server,

useful to know in development.
admin_password[R]
admin_user[R]
connection[R]
instrumentation_service[R]
member_search_strategy[R]
membership_validator[R]
port[R]
search_domains[R]
uid[R]
user_search_strategy[R]
virtual_attributes[R]

Public Class Methods

new(options = {}) click to toggle source

Build a new GitHub::Ldap instance

## Connection

host: required string ldap server host address port: required string or number ldap server port hosts: an enumerable of pairs of hosts and corresponding ports with

which to attempt opening connections (default [[host, port]]). Overrides
host and port if set.

encryption: optional string. `ssl` or `tls`. nil by default admin_user: optional string ldap administrator user dn for authentication admin_password: optional string ldap administrator user password

## Behavior

uid: optional field name used to authenticate users. Defaults to `sAMAccountName` (what ActiveDirectory uses) virtual_attributes: optional. boolean true to use server's virtual attributes. Hash to specify custom mapping. Default false. recursive_group_search_fallback: optional boolean whether membership checks should recurse into nested groups when virtual attributes aren't enabled. Default false. posix_support: optional boolean `posixGroup` support. Default true. search_domains: optional array of string bases to search through

## Diagnostics

instrumentation_service: optional ActiveSupport::Notifications compatible object

# File lib/github/ldap.rb, line 83
def initialize(options = {})
  @uid = options[:uid] || "sAMAccountName"

  # Keep a reference to these as default auth for a Global Catalog if needed
  @admin_user = options[:admin_user]
  @admin_password = options[:admin_password]
  @port = options[:port]

  @connection = Net::LDAP.new({
    host: options[:host],
    port: options[:port],
    hosts: options[:hosts],
    instrumentation_service: options[:instrumentation_service]
  })

  if options[:admin_user] && options[:admin_password]
    @connection.authenticate(options[:admin_user], options[:admin_password])
  end

  if encryption = check_encryption(options[:encryption])
    @connection.encryption(encryption)
  end

  configure_virtual_attributes(options[:virtual_attributes])

  # enable fallback recursive group search unless option is false
  @recursive_group_search_fallback = (options[:recursive_group_search_fallback] != false)

  # enable posixGroup support unless option is false
  @posix_support = (options[:posix_support] != false)

  # search_domains is a connection of bases to perform searches
  # when a base is not explicitly provided.
  @search_domains = Array(options[:search_domains])

  # configure both the membership validator and the member search strategies
  configure_search_strategy(options[:search_strategy])

  # configure the strategy used by Domain#user? to look up a user entry for login
  configure_user_search_strategy(options[:user_search_strategy])

  # enables instrumenting queries
  @instrumentation_service = options[:instrumentation_service]
end
server_tmp() click to toggle source

Determine the temporal directory where the ldap server lives. If there is no temporal directory in the environment we create one in the base path.

Returns the path to the temporal directory.

# File lib/github/ldap/server.rb, line 57
def self.server_tmp
  tmp = ENV['TMPDIR'] || ENV['TEMPDIR']

  if tmp.nil?
    tmp = 'tmp'
    Dir.mkdir(tmp) unless File.directory?('tmp')
  end

  tmp
end
start_server(options = {}) click to toggle source

Start a testing server. If there is already a server initialized it doesn't do anything.

options: is a hash with the custom options for the server.

# File lib/github/ldap/server.rb, line 33
def self.start_server(options = {})
  @server_options = DEFAULT_SERVER_OPTIONS.merge(options)

  @server_options[:allow_anonymous] ||= false
  @server_options[:ldif]              = @server_options[:user_fixtures]
  @server_options[:domain]            = @server_options[:user_domain]
  @server_options[:tmpdir]          ||= server_tmp

  @server_options[:quiet] = false if @server_options[:verbose]

  @ldap_server = Ladle::Server.new(@server_options)
  @ldap_server.start
end
stop_server() click to toggle source

Stop the testing server. If there is no server started this method doesn't do anything.

# File lib/github/ldap/server.rb, line 49
def self.stop_server
  ldap_server && ldap_server.stop
end

Public Instance Methods

capabilities() click to toggle source

Internal: Searches the host LDAP server's Root DSE for capabilities and extensions.

Returns a Net::LDAP::Entry object.

# File lib/github/ldap.rb, line 223
def capabilities
  @capabilities ||=
    instrument "capabilities.github_ldap" do |payload|
      begin
        @connection.search_root_dse
      rescue Net::LDAP::LdapError => error
        payload[:error] = error
        # stubbed result
        Net::LDAP::Entry.new
      end
    end
end
check_encryption(encryption) click to toggle source

Internal - Determine whether to use encryption or not.

encryption: is the encryption method, either 'ssl', 'tls', 'simple_tls' or 'start_tls'.

Returns the real encryption type.

# File lib/github/ldap.rb, line 241
def check_encryption(encryption)
  return unless encryption

  case encryption.downcase.to_sym
  when :ssl, :simple_tls
    :simple_tls
  when :tls, :start_tls
    :start_tls
  end
end
configure_member_search_strategy(strategy = nil) click to toggle source

Internal: Configure the member search strategy.

If no known strategy is provided, detects ActiveDirectory capabilities or falls back to the Recursive strategy by default.

Returns the selected strategy Class.

# File lib/github/ldap.rb, line 336
def configure_member_search_strategy(strategy = nil)
  @member_search_strategy =
    case strategy.to_s
    when "classic"
      GitHub::Ldap::MemberSearch::Classic
    when "recursive"
      GitHub::Ldap::MemberSearch::Recursive
    when "active_directory"
      GitHub::Ldap::MemberSearch::ActiveDirectory
    else
      # fallback to detection, defaulting to recursive strategy
      if active_directory_capability?
        GitHub::Ldap::MemberSearch::ActiveDirectory
      else
        GitHub::Ldap::MemberSearch::Recursive
      end
    end
end
configure_membership_validation_strategy(strategy = nil) click to toggle source

Internal: Configure the membership validation strategy.

If no known strategy is provided, detects ActiveDirectory capabilities or falls back to the Recursive strategy by default.

Returns the membership validator strategy Class.

# File lib/github/ldap.rb, line 288
def configure_membership_validation_strategy(strategy = nil)
  @membership_validator =
    case strategy.to_s
    when "classic"
      GitHub::Ldap::MembershipValidators::Classic
    when "recursive"
      GitHub::Ldap::MembershipValidators::Recursive
    when "active_directory"
      GitHub::Ldap::MembershipValidators::ActiveDirectory
    else
      # fallback to detection, defaulting to recursive strategy
      if active_directory_capability?
        GitHub::Ldap::MembershipValidators::ActiveDirectory
      else
        GitHub::Ldap::MembershipValidators::Recursive
      end
    end
end
configure_search_strategy(strategy = nil) click to toggle source

Internal: Configure the member search and membership validation strategies.

TODO: Inline the logic in these two methods here.

Returns nothing.

# File lib/github/ldap.rb, line 274
def configure_search_strategy(strategy = nil)
  # configure which strategy should be used to validate user membership
  configure_membership_validation_strategy(strategy)

  # configure which strategy should be used for member search
  configure_member_search_strategy(strategy)
end
configure_user_search_strategy(strategy) click to toggle source

Internal: Set the user search strategy that will be used by

Domain#user?.

strategy - Can be either 'default' or 'global_catalog'.

'default' strategy will search the configured
domain controller with a search base relative
to the controller's domain context.
'global_catalog' will search the entire forest
using Active Directory's Global Catalog
functionality.
# File lib/github/ldap.rb, line 317
def configure_user_search_strategy(strategy)
  @user_search_strategy =
    case strategy.to_s
    when "default"
      GitHub::Ldap::UserSearch::Default.new(self)
    when "global_catalog"
      GitHub::Ldap::UserSearch::ActiveDirectory.new(self)
    else
      GitHub::Ldap::UserSearch::Default.new(self)
    end
end
configure_virtual_attributes(attributes) click to toggle source

Internal - Configure virtual attributes for this server. If the option is `true`, we'll use the default virual attributes. If it's a Hash we'll map the attributes in the hash.

attributes: is the option set when Ldap is initialized.

Returns a VirtualAttributes.

# File lib/github/ldap.rb, line 259
def configure_virtual_attributes(attributes)
  @virtual_attributes = if attributes == true
    VirtualAttributes.new(true)
  elsif attributes.is_a?(Hash)
    VirtualAttributes.new(true, attributes)
  else
    VirtualAttributes.new(false)
  end
end
domain(base_name) click to toggle source

Public - Creates a new domain object to perform operations

base_name: is the dn of the base root.

Returns a new Domain object.

# File lib/github/ldap.rb, line 164
def domain(base_name)
  Domain.new(self, base_name, @uid)
end
group(base_name) click to toggle source

Public - Creates a new group object to perform operations

base_name: is the dn of the base root.

Returns a new Group object. Returns nil if the dn is not in the server.

# File lib/github/ldap.rb, line 174
def group(base_name)
  entry = domain(base_name).bind
  return unless entry

  load_group(entry)
end
load_group(group_entry) click to toggle source

Public - Create a new group object based on a Net::LDAP::Entry.

group_entry: is a Net::LDAP::Entry.

Returns a Group, PosixGroup or VirtualGroup object.

# File lib/github/ldap.rb, line 186
def load_group(group_entry)
  if @virtual_attributes.enabled?
    VirtualGroup.new(self, group_entry)
  elsif posix_support_enabled? && PosixGroup.valid?(group_entry)
    PosixGroup.new(self, group_entry)
  else
    Group.new(self, group_entry)
  end
end
posix_support_enabled?() click to toggle source

Public - Whether membership checks should include posixGroup filter conditions on `memberUid`. Configurable since some LDAP servers don't handle unsupported attribute queries gracefully.

Enable by passing :posix_support => true.

Returns true, false, or nil (assumed false).

# File lib/github/ldap.rb, line 145
def posix_support_enabled?
  @posix_support
end
recursive_group_search_fallback?() click to toggle source

Public - Whether membership checks should recurse into nested groups when virtual attributes aren't enabled. The fallback search has poor performance characteristics in some cases, in which case this should be disabled by passing :recursive_group_search_fallback => false.

Returns true or false.

# File lib/github/ldap.rb, line 134
def recursive_group_search_fallback?
  @recursive_group_search_fallback
end
test_connection() click to toggle source

Public - Utility method to check if the connection with the server can be stablished. It tries to bind with the ldap auth default configuration.

Returns an OpenStruct with `code` and `message`. If `code` is 0, the operation succeeded and there is no message.

# File lib/github/ldap.rb, line 154
def test_connection
  @connection.bind
  last_operation_result
end

Private Instance Methods

active_directory_capability?() click to toggle source

Internal: Detect whether the LDAP host is an ActiveDirectory server.

See: msdn.microsoft.com/en-us/library/cc223359.aspx.

Returns true if the host is an ActiveDirectory server, false otherwise.

# File lib/github/ldap.rb, line 360
def active_directory_capability?
  capabilities[:supportedcapabilities].include?(ACTIVE_DIRECTORY_V51_OID)
end