Class: ActiveDirectory::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/active_directory/base.rb

Overview

The ActiveDirectory module contains the classes used for communicating with Active Directory LDAP servers. ActiveDirectory::Base is the basis from which all classes derive.

Direct Known Subclasses

Group, User

Constant Summary collapse

@@server_settings =

Configuration

{
  :host     => "localhost",
  :port     => 389,
  :username => nil,
  :password => nil,
  :domain   => nil,
  :base_dn  => nil
}

Class Method Summary collapse

Class Method Details

.closeObject

Unbinds (if bound) and closes the current connection.



119
120
121
122
123
124
125
# File 'lib/active_directory/base.rb', line 119

def self.close
  begin
    @@connection.unbind unless @@connection.nil?
  rescue
  end
  @@connection = nil
end

.connectObject

Opens and returns a connection to the Active Directory instance. By default, secure connections will be attempted first while gracefully falling back to lesser secure methods.

The order by which connections are attempted are: TLS, SSL, unencrypted.

Calling #connection will automatically call this method if a connection has not already been established.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/active_directory/base.rb', line 74

def self.connect

  host = @@server_settings[:host]
  port = @@server_settings[:port] || 389

  # Attempt to connect using TLS
  begin
    connection = LDAP::SSLConn.new(host, port, true)
    connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
    bind(connection)
    logger.info("ActiveDirectory: Connected to #{@@server_settings[:host]} using TLS...") unless logger.nil?
  rescue
    logger.debug("ActiveDirectory: Failed to connect to #{@@server_settings[:host]} using TLS!") unless logger.nil?
    # Attempt to connect using SSL
    begin
      connection = LDAP::SSLConn.new(host, port, false)
      connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
      bind(connection)
      logger.info("ActiveDirectory: Connected to #{@@server_settings[:host]} over SSL...") unless logger.nil?
    rescue
      logger.debug("ActiveDirectory: Failed to connect to #{@@server_settings[:host]} over SSL!") unless logger.nil?
      # Attempt to connect without encryption
      begin
        connection = LDAP::Conn.new(host, port)
        connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
        bind(connection)
        logger.info("ActiveDirectory: Connected to #{@@server_settings[:host]} without encryption!") unless logger.nil?
      rescue
        logger.error("ActiveDirectory: Failed to connect to #{@@server_settings[:host]}!") unless logger.nil?
        puts "EXCEPTION: #{$!}"
        connection = nil
        raise
      end
    end
  end

  connection.set_option(LDAP::LDAP_OPT_REFERRALS, 0)

  connection

end

.connectionObject

Returns a connection to the configured Active Directory instance. If a connection is not already established, it will attempt to establish the connection.



60
61
62
# File 'lib/active_directory/base.rb', line 60

def self.connection
  @@connection ||= connect
end

.find(*args) ⇒ Object

Search interface for querying for objects within the directory, such as users and groups. Searching the directory is quite similar to a normal LDAP search, but with a better API.

If you call this method from User or Group, it will narrow your searches to those specific objects by default. Calling this method on Base will return objects of any class and provides the most flexibility.

Searching Users

Users may be located within the directory by their distinguished name (DN), their username (sAMAccountName), or through any other valid LDAP filter and optional search base.

# Load all users (including disabled) within the default Base DN.
all_users      = ActiveDirectory::User.find(:all)

# Load all disabled users within the default Base DN.
disabled_users = ActiveDirectory::User.find(:all,
                   :filter => "(userAccountControl=514)")

# Load all users who are in the Managers organizational unit whose
# accounts are not disabled.
managers       = ActiveDirectory::User.find(:all,
                   :base   => "OU=Managers,DC=example,DC=com",
                   :filter => "(userAccountControl=512)")

# Load the user "John Doe" by his sAMAccountName.
user = ActiveDirectory::User.find("jdoe")

# Load the user "John Doe" by his distinguished name (DN).
user = ActiveDirectory::User.find("CN=John Doe,CN=Users,DC=example,DC=com")

Searching Groups

Groups may be located within the diretctory by their distinguished name (DN) or through any other valid LDAP filter and optional search base.

# Load all groups within the default Base DN.
all_groups = ActiveDirectory::Group.find(:all)

# Load the "Developers" group by its distinguished name (DN).
developers = ActiveDirectory::Group.find("CN=Developers,DC=example,DC=com")

More Advanced Examples

By calling ActiveDirectory::Base#find you can query objects across classes, allowing you to pull in both Groups and Users that match your criteria.

# Load all Contacts
contacts = ActiveDirectory::Base.find(:all,
             :filter => "(&(objectClass=User)(objectCategory=Contact))")


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/active_directory/base.rb', line 191

def self.find(*args)

  options    = extract_options_from_args!(args)
  attributes = [ "distinguishedName", "objectClass" ]

  # Determine the appropriate search filter
  if self.name == "ActiveDirectory::Base"
    if options[:filter]
      filter = options[:filter]
    else
      filter = "(|(&(objectCategory=Person)(objectClass=User))(objectClass=Group))"
    end
  else
    subklass = class_name_of_active_directory_descendent(self)
    if subklass == "ActiveDirectory::User"
      filter = "(&(objectCategory=Person)(objectClass=User)#{options[:filter]})"
    elsif subklass == "ActiveDirectory::Group"
      filter = "(&(objectClass=Group)#{options[:filter]})"
    end
  end

  # Determine the appropriate search base
  base_dn = options[:base] ? options[:base] : @@server_settings[:base_dn]

  # Determine the appropriate scope
  scope = options[:scope] ? options[:scope] : LDAP::LDAP_SCOPE_SUBTREE

  # Load all matching objects
  if args.first == :all

    logger.debug "Searching Active Directory" \
                 " - Filter: #{filter}" \
                 " - Search Base: #{base_dn} " \
                 " - Scope: #{scope}"

    # Perform search
    entries = self.search(base_dn, scope, filter, attributes)

    result = Array.new
    unless entries.nil?
      for entry in entries
        if entry['objectClass'].include? "person"
          result << User.new(entry['distinguishedName'][0])
        elsif entry['objectClass'].include? "group"
          result << Group.new(entry['distinguishedName'][0])
        end
      end
    end
    result

  else

    # Load a single matching object by either a common name or a
    # sAMAccountName
    if args.first =~ /(CN|cn)=/
      base_dn = args.first
    else
      filter = "(&(objectCategory=Person)(objectClass=User)(samAccountName=#{args.first})#{options[:filter]})"
    end

    logger.debug "Searching Active Directory" \
                 " - Filter: #{filter}" \
                 " - Search Base: #{base_dn} " \
                 " - Scope: #{scope}"

    begin
      entry = self.search(base_dn, scope, filter, attributes)
    rescue
      if $!.message == "No such object"
        raise UnknownUserError
      else
        raise
      end
    end

    entry = entry[0]
    if entry['objectClass'].include? "person"
      User.new(entry['distinguishedName'][0])
    elsif entry['objectClass'].include? "group"
      Group.new(entry['distinguishedName'][0])
    end

  end

end

.loggerObject



51
52
53
# File 'lib/active_directory/base.rb', line 51

def self.logger
  @@logger || Logger.new(STDOUT)
end

.reconnectObject

Attempts to reconnect to the server by closing the existing connection and reconnecting.



131
132
133
134
# File 'lib/active_directory/base.rb', line 131

def self.reconnect
  close
  @@connection = connect
end