Class: JSS::LDAPServer

Inherits:
APIObject show all
Defined in:
lib/jss/api_object/ldap_server.rb,
lib/jss.rb

Overview

An LDAP server in the JSS.

This class doesn’t curretly provide creation or updaing of LDAP server definitions in the JSS. Please use the JSS web UI.

However, it does provide methods for querying users and usergroups from LDAP servers, and checking group membership.

When an LDAPServer instance is created, if it uses anonymous binding for lookups (the Authentication Type is set to ‘none’) then the LDAP connection is established immediately. Otherwise, you must use the #connect method, and provide the appropriate password for the lookup account defined.

Since LDAP server connections are used to verify the validity of LDAP users & groups used in scopes, if you don’t connect to all LDAP servers before modifying any scope’s user & group limitations or exceptions, those new values may not be verifiable. Unverified limitations and exceptions, when sent to the API, will result in a REST 409 Conflict error if the user or group doesn’t exist. Unfortunately, 409 Conflict errors are very generic and don’t indicate the source of the problem (in this case, a non-existent user or group limitation or exception to the scope). The Scopable module tries to catch these errors and raise a more useful exception when they happen.

The class method LDAPServer.all_ldaps returns a Hash of JSS::LDAPServer instances. one for each server defined in the JSS.

The class methods LDAPServer.user_in_ldap? and LDAPServer.group_in_ldap? can be used to check all defined LDAP servers for a user or group. They are used by Scopable::Scope when adding user and groups to scope limitations and exceptions.

Within an LDAPServer instance, the methods #find_user and #find_group will return all matches in the server for a given search term.

See Also:

Constant Summary collapse

RSRC_BASE =

The base for REST resources of this class

"ldapservers"
RSRC_LIST_KEY =

the hash key used for the JSON list output of all objects in the JSS

:ldap_servers
RSRC_OBJECT_KEY =

The hash key used for the JSON object output. It’s also used in various error messages

:ldap_server
VALID_DATA_KEYS =

these keys, as well as :id and :name, are present in valid API JSON data for this class

[]
DEFAULT_PORT =

the default LDAP port

389
SEARCH_SCOPES =

possible values for search scope

["All Subtrees", "First Level Only"]
AUTH_TYPES =

possible authentication types

{'none' => :anonymous, 'simple' => :simple, 'CRAM-MD5' => :cram_md5, 'DIGEST-MD5' => :digest_md5 }
REFERRAL_RESPONSES =

possible referral responses

['', nil, 'follow', 'ignore']
OBJECT_CLASS_MAPPING_OPTIONS =

possible objectclass mapping options

["any", "all"]
OBJECT_HISTORY_OBJECT_TYPE =

the object type for this object in the object history table. See APIObject#add_object_history_entry

80

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ LDAPServer

See JSS::APIObject#initialize



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/jss/api_object/ldap_server.rb', line 268

def initialize (args = {})
  require 'net/ldap'
  super

  @hostname = @init_data[:connection][:hostname]
  @port = @init_data[:connection][:port]
  @use_ssl = @init_data[:connection][:use_ssl]
  @authentication_type = AUTH_TYPES[@init_data[:connection][:authentication_type]]
  @open_close_timeout = @init_data[:connection][:open_close_timeout]
  @search_timeout = @init_data[:connection][:search_timeout]
  @referral_response = @init_data[:connection][:referral_response]
  @use_wildcards = @init_data[:connection][:use_wildcards]

  @lookup_dn = @init_data[:connection][:account][:distinguished_username]
  @lookup_pw_sha256 = @init_data[:connection][:account][:password_sha256]

  @user_mappings = @init_data[:mappings_for_users ][:user_mappings]
  @user_group_mappings = @init_data[:mappings_for_users ][:user_group_mappings]
  @user_group_membership_mappings = @init_data[:mappings_for_users ][:user_group_membership_mappings]

  # the ldap attributes to retrieve with user lookups
  # (all those defined in the user mappings)
  @user_attrs_to_get = {
    :username => @user_mappings[:map_username],
    :user_id => @user_mappings[:map_user_id],
    :department => @user_mappings[:map_department],
    :building => @user_mappings[:map_building],
    :room => @user_mappings[:map_room],
    :realname => @user_mappings[:map_realname],
    :phone => @user_mappings[:map_phone],
    :email_address => @user_mappings[:map_email_address],
    :position => @user_mappings[:map_position],
    :user_uuid => @user_mappings[:map_user_uuid]
  }.delete_if{|k,v| v.nil? }

  # and for groups....
  @user_group_attrs_to_get = {
    :group_id => @user_group_mappings[:map_group_id],
    :group_name => @user_group_mappings[:map_group_name],
    :group_uuid => @user_group_mappings[:map_group_uuid]
  }.delete_if{|k,v| v.nil? }

  @connection = nil
  @connected = false

  # If we are using anonymous binding, connect now
  connect if @authentication_type == :anonymous
end

Instance Attribute Details

#authentication_typeString (readonly)

Returns what authentication method should be used?.

Returns:

  • (String)

    what authentication method should be used?



177
178
179
# File 'lib/jss/api_object/ldap_server.rb', line 177

def authentication_type
  @authentication_type
end

#connectedBoolean (readonly) Also known as: connected?

Returns we we connected to this server at the moment?.

Returns:

  • (Boolean)

    we we connected to this server at the moment?



259
260
261
# File 'lib/jss/api_object/ldap_server.rb', line 259

def connected
  @connected
end

#hostanmeString (readonly)

Returns the hostname of the server.

Returns:

  • (String)

    the hostname of the server



168
169
170
# File 'lib/jss/api_object/ldap_server.rb', line 168

def hostanme
  @hostanme
end

#lookup_dnString (readonly)

Returns the Distinguished Name of the account used for connections/lookups?.

Returns:

  • (String)

    the Distinguished Name of the account used for connections/lookups?



180
181
182
# File 'lib/jss/api_object/ldap_server.rb', line 180

def lookup_dn
  @lookup_dn
end

#lookup_pw_sha256String (readonly)

Returns the password for the connection/lookup account, as a SHA256 digest.

Returns:

  • (String)

    the password for the connection/lookup account, as a SHA256 digest.



183
184
185
# File 'lib/jss/api_object/ldap_server.rb', line 183

def lookup_pw_sha256
  @lookup_pw_sha256
end

#open_close_timeoutInteger (readonly)

Returns timeout, in seconds, for opening LDAP connections.

Returns:

  • (Integer)

    timeout, in seconds, for opening LDAP connections



186
187
188
# File 'lib/jss/api_object/ldap_server.rb', line 186

def open_close_timeout
  @open_close_timeout
end

#portInteger (readonly)

Returns the port for ldap.

Returns:

  • (Integer)

    the port for ldap



171
172
173
# File 'lib/jss/api_object/ldap_server.rb', line 171

def port
  @port
end

#referral_responseString (readonly)

Returns the referral response from the server.

Returns:

  • (String)

    the referral response from the server



192
193
194
# File 'lib/jss/api_object/ldap_server.rb', line 192

def referral_response
  @referral_response
end

#search_timeoutInteger (readonly)

Returns timeout, in seconds, for search queries.

Returns:

  • (Integer)

    timeout, in seconds, for search queries



189
190
191
# File 'lib/jss/api_object/ldap_server.rb', line 189

def search_timeout
  @search_timeout
end

#use_sslBoolean (readonly)

Returns should the connection use ssl?.

Returns:

  • (Boolean)

    should the connection use ssl?



174
175
176
# File 'lib/jss/api_object/ldap_server.rb', line 174

def use_ssl
  @use_ssl
end

#use_wildcardsBoolean (readonly)

Returns should searches use wildcards?.

Returns:

  • (Boolean)

    should searches use wildcards?



195
196
197
# File 'lib/jss/api_object/ldap_server.rb', line 195

def use_wildcards
  @use_wildcards
end

#user_group_mappingsHash<Symbol=>String> (readonly)

The LDAP attributes mapped to various user group data

The hash keys are:

  • :search_base =>

  • :search_scope =>

  • :object_classes =>

  • :map_object_class_to_any_or_all =>

  • :map_group_id =>

  • :map_group_name =>

  • :map_group_uuid =>

Returns:



235
236
237
# File 'lib/jss/api_object/ldap_server.rb', line 235

def user_group_mappings
  @user_group_mappings
end

#user_group_membership_mappingsHash<Symbol=>String> (readonly)

The LDAP attributes used to identify a user as a member of a group

The hash keys are:

  • :user_group_membership_stored_in =>

  • :map_user_membership_use_dn =>

  • :map_group_membership_to_user_field =>

  • :group_id =>

  • :map_object_class_to_any_or_all =>

  • :append_to_username =>

  • :username =>

  • :object_classes =>

  • :use_dn =>

  • :search_base =>

  • :recursive_lookups =>

  • :search_scope =>

  • :map_user_membership_to_group_field =>

Returns:



256
257
258
# File 'lib/jss/api_object/ldap_server.rb', line 256

def user_group_membership_mappings
  @user_group_membership_mappings
end

#user_mappingsHash<Symbol=>String> (readonly)

The LDAP attributes mapped to various user data

The hash keys are:

  • :search_base =>

  • :search_scope =>

  • :object_classes =>

  • :map_object_class_to_any_or_all =>

  • :map_username =>

  • :map_user_id =>

  • :map_department =>

  • :map_building =>

  • :map_room =>

  • :map_realname =>

  • :map_phone =>

  • :map_email_address =>

  • :map_position =>

  • :map_user_uuid =>

  • :append_to_email_results =>

Returns:



219
220
221
# File 'lib/jss/api_object/ldap_server.rb', line 219

def user_mappings
  @user_mappings
end

Class Method Details

.all_ldaps(refresh = false, api: JSS.api) ⇒ Hash{String => JSS::LDAPServer}

DEPRECATED: Please Use ::all_objects

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the LDAP server data be re-read from the API?

Returns:



89
90
91
92
93
# File 'lib/jss/api_object/ldap_server.rb', line 89

def self.all_ldaps(refresh = false, api: JSS.api)
  hash = {}
  all_objects(refresh, api: api) { |ls| hash[ls.name] = s }
  hash
end

.group_in_ldap?(group, api: JSS.api) ⇒ Boolean

Returns does the group exist in any LDAP server?.

Parameters:

  • group (String)

    a group to search for in all LDAP servers

Returns:

  • (Boolean)

    does the group exist in any LDAP server?



113
114
115
116
117
118
119
# File 'lib/jss/api_object/ldap_server.rb', line 113

def self.group_in_ldap? (group, api: JSS.api)
  all_objects(refresh, api: api).each do |ldap|
    next if ldap.find_group(group, :exact).empty?
    return true
  end
  false
end

.user_in_ldap?(user, api: JSS.api) ⇒ Boolean

Returns does the user exist in any LDAP server?.

Parameters:

  • user (String)

    a username to search for in all LDAP servers

Returns:

  • (Boolean)

    does the user exist in any LDAP server?



100
101
102
103
104
105
106
# File 'lib/jss/api_object/ldap_server.rb', line 100

def self.user_in_ldap?(user, api: JSS.api)
  all_objects(refresh, api: api).each do |ldap|
    next if ldap.find_user(user, :exact).empty?
    return true
  end
  false
end

Instance Method Details

#check_membership(user, group) ⇒ Boolean?

TODO:

Implement checking groups membership in ‘other’ ldap area

Returns is the user a member? Nil if unable to check.

Parameters:

  • user (String)

    the username to check for memebership in the group

  • group (String)

    the group name to see if the user is a member

Returns:

  • (Boolean, nil)

    is the user a member? Nil if unable to check

Raises:



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/jss/api_object/ldap_server.rb', line 446

def check_membership(user, group)

  raise JSS::InvalidConnectionError, "Not connected to LDAP server '#{@name}'. Please use #connect first." unless @connected

  found_user = find_user(user, :exact)[0]
  found_group = find_group(group, :exact)[0]

  raise JSS::NoSuchItemError, "No user '#{user}' in LDAP." unless found_user
  raise JSS::NoSuchItemError, "No group '#{group}' in LDAP." unless found_group

  if @user_group_membership_mappings[:user_group_membership_stored_in] == "group object"
    if @user_group_membership_mappings[:map_user_membership_use_dn]
      return found_group[:members].include? found_user[:dn]
    else
      return found_group[:members].include? user
    end


  elsif @user_group_membership_mappings[:user_group_membership_stored_in] == "user object"
    if @user_group_membership_mappings[:use_dn]
      return found_user[:groups].include? found_group[:dn]
    else
      return found_user[:groups].include? group
    end


  else
    ### To do!!
    return nil
    # implement a search based on the "other" settings
    # This will be 3 searchs
    # - one for the username mapping in users
    # - one for the gid in groups
    # - one for a record linking them in the "other" search base
  end
end

#connect(pw = nil) ⇒ Boolean

The connect to this LDAP server for subsequent use of the #find_user, #find_group and #check_membership methods

Parameters:

  • pw (String, Symbol) (defaults to: nil)

    the LDAP connection password for this server. Can be nil if authentication type is ‘none’. If :prompt, the user is promted on the commandline to enter the password for the :user. If :stdin#, the password is read from a line of std in represented by the digit at #, so :stdin3 reads the passwd from the third line of standard input. defaults to line 2, if no digit is supplied. see JSS.stdin

Returns:

  • (Boolean)

    did we connect to the LDAP server with the defined credentials



498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
# File 'lib/jss/api_object/ldap_server.rb', line 498

def connect(pw = nil)

  unless @authentication_type == :anonymous
    # how do we get the password?
    password = if pw == :prompt
      JSS.prompt_for_password "Enter the password for the LDAP connection account '#{@lookup_dn}':"
    elsif pw.is_a?(Symbol) and pw.to_s.start_with?('stdin')
      pw.to_s =~ /^stdin(\d+)$/
      line = $1
      line ||= 2
      JSS.stdin line
    else
      pw
    end


    raise JSS::InvalidDataError, "Incorrect password for LDAP connection account '#{@lookup_dn}'" unless @lookup_pw_sha256 == Digest::SHA2.new(256).update(password.to_s).to_s
  end # unless

  @connection = Net::LDAP.new :host => @hostname, :port => @port, :auth => {:method => @authentication_type, :username => @lookup_dn, :password => password }

  @connected = true
end

#find_group(group, exact = false, additional_filter = nil) ⇒ Array<Hash>

Returns The @user_group_attrs_to_get for all groups matching the query.

Parameters:

  • group (String)

    the group name to search for

  • exact (Boolean) (defaults to: false)

    if true, force an exact match, otherwuse use wildcards if @use_wildcards is true

  • additional_filter (Net::LDAP::Fliter) (defaults to: nil)

    an additional filter to be AND’d to the existing filter.

Returns:

  • (Array<Hash>)

    The @user_group_attrs_to_get for all groups matching the query

Raises:



389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/jss/api_object/ldap_server.rb', line 389

def find_group(group, exact = false, additional_filter = nil)

  raise JSS::InvalidConnectionError, "Not connected to LDAP server '#{@name}'. Please use #connect first." unless @connected

  if @use_wildcards and not exact
    group_filter = Net::LDAP::Filter.contains(@user_group_mappings[:map_group_name], group)
  else
    group_filter = Net::LDAP::Filter.eq(@user_group_mappings[:map_group_name], group)
  end

  # limit the object classes
  ocs = @user_group_mappings[:object_classes].to_s.chomp.split(/,\s*/)
  anyall = @user_group_mappings[:map_object_class_to_any_or_all]
  oc_filter =  Net::LDAP::Filter.eq("objectclass", ocs.shift)
  ocs.each do |oc|
    if anyall == "any"
      oc_filter = oc_filter | Net::LDAP::Filter.eq("objectclass", oc)
    else
      oc_filter = oc_filter & Net::LDAP::Filter.eq("objectclass", oc)
    end
  end

  full_filter = oc_filter & group_filter
  full_filter = full_filter & additional_filter if additional_filter
  treebase = @user_group_mappings[:search_base]
  ldap_attribs = @user_group_attrs_to_get.values

  # should we grab membership from the group?
  if @user_group_membership_mappings[:user_group_membership_stored_in] == "group object" and \
    @user_group_membership_mappings[:map_user_membership_to_group_field]
    get_members = true
    ldap_attribs << @user_group_membership_mappings[:map_user_membership_to_group_field]
  end

  results = []
  @connection.search(:base => treebase, :filter => full_filter, :attributes => ldap_attribs ) do |entry|
    hash = {:dn => entry.dn}
    @user_group_attrs_to_get.each do |k,attr|
      hash[k] = entry[attr][0]
    end
    hash[:members] = entry[@user_group_membership_mappings[:map_user_membership_to_group_field]] if get_members
    # to do, if the members are dns, convert to usernames
    results << hash
  end
  results
end

#find_user(user, exact = false, additional_filter = nil) ⇒ Array<Hash>

Returns The @user_attrs_to_get for all usernames matching the query.

Parameters:

  • user (String)

    the username to search for

  • exact (Boolean) (defaults to: false)

    if true, force an exact match, otherwise use wildcards if @use_wildcards is true

  • additional_filter (Net::LDAP::Fliter) (defaults to: nil)

    an additional filter to be AND’d to the existing filter.

Returns:

  • (Array<Hash>)

    The @user_attrs_to_get for all usernames matching the query

Raises:



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/jss/api_object/ldap_server.rb', line 331

def find_user(user, exact = false, additional_filter = nil)

  raise JSS::InvalidConnectionError, "Not connected to LDAP server '#{@name}'. Please use #connect first." unless @connected

  if @use_wildcards and not exact
    user_filter = Net::LDAP::Filter.contains(@user_mappings[:map_username], user)
  else
    user_filter = Net::LDAP::Filter.eq(@user_mappings[:map_username], user)
  end

  # limit the object classes
  ocs = @user_mappings[:object_classes].to_s.chomp.split(/,\s*/)
  anyall = @user_mappings[:map_object_class_to_any_or_all]
  oc_filter =  Net::LDAP::Filter.eq("objectclass", ocs.shift)
  ocs.each do |oc|
    if anyall == "any"
      oc_filter = oc_filter | Net::LDAP::Filter.eq("objectclass", oc)
    else
      oc_filter = oc_filter & Net::LDAP::Filter.eq("objectclass", oc)
    end
  end

  full_filter = oc_filter & user_filter
  full_filter = full_filter & additional_filter if additional_filter
  treebase = @user_mappings[:search_base]
  ldap_attribs = @user_attrs_to_get.values

  # should we grab membership from the user?
  if @user_group_membership_mappings[:user_group_membership_stored_in] == "user object" and \
    @user_group_membership_mappings[:map_group_membership_to_user_field]
    get_groups = true
    ldap_attribs << @user_group_membership_mappings[:map_group_membership_to_user_field]
  end

  results = []

  @connection.search(:base => treebase, :filter => full_filter, :attributes => ldap_attribs ) do |entry|
    userhash = {:dn => entry.dn}
    @user_attrs_to_get.each do |k,attr|
      userhash[k] = entry[attr][0]
    end
    userhash[:groups] = entry[@user_group_membership_mappings[:map_group_membership_to_user_field]] if get_groups
    # to do - if the groups are dns, convert to groupnames
    results << userhash
  end
  results
end