Class: JSS::LDAPServer

Inherits:
APIObject show all
Defined in:
lib/jss-api/api_object/ldap_server.rb,
lib/jss-api.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"]
@@all_ldaps =

Class Variables

nil

Constants inherited from APIObject

APIObject::DEFAULT_LOOKUP_KEYS, APIObject::REQUIRED_DATA_KEYS

Instance Attribute Summary collapse

Attributes inherited from APIObject

#id, #in_jss, #name, #rest_rsrc

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from APIObject

all, all_ids, all_names, #delete, get_name, map_all_ids_to, #save, xml_list

Constructor Details

#initialize(args = {}) ⇒ LDAPServer

See JSS::APIObject#initialize



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
316
317
318
# File 'lib/jss-api/api_object/ldap_server.rb', line 271

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?



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

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?



262
263
264
# File 'lib/jss-api/api_object/ldap_server.rb', line 262

def connected
  @connected
end

#hostanmeString (readonly)

Returns the hostname of the server.

Returns:

  • (String)

    the hostname of the server



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

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?



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

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.



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

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



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

def open_close_timeout
  @open_close_timeout
end

#portInteger (readonly)

Returns the port for ldap.

Returns:

  • (Integer)

    the port for ldap



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

def port
  @port
end

#referral_responseString (readonly)

Returns the referral response from the server.

Returns:

  • (String)

    the referral response from the server



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

def referral_response
  @referral_response
end

#search_timeoutInteger (readonly)

Returns timeout, in seconds, for search queries.

Returns:

  • (Integer)

    timeout, in seconds, for search queries



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

def search_timeout
  @search_timeout
end

#use_sslBoolean (readonly)

Returns should the connection use ssl?.

Returns:

  • (Boolean)

    should the connection use ssl?



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

def use_ssl
  @use_ssl
end

#use_wildcardsBoolean (readonly)

Returns should searches use wildcards?.

Returns:

  • (Boolean)

    should searches use wildcards?



198
199
200
# File 'lib/jss-api/api_object/ldap_server.rb', line 198

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:



238
239
240
# File 'lib/jss-api/api_object/ldap_server.rb', line 238

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:



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

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:



222
223
224
# File 'lib/jss-api/api_object/ldap_server.rb', line 222

def user_mappings
  @user_mappings
end

Class Method Details

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

Returns JSS::LDAPServer instances for all defined servers.

Parameters:

  • refresh (Boolean) (defaults to: false)

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

Returns:



97
98
99
100
101
102
103
104
105
# File 'lib/jss-api/api_object/ldap_server.rb', line 97

def self.all_ldaps(refresh = false)
  @@all_ldaps = nil if refresh
  return @@all_ldaps if @@all_ldaps
  
  @@all_ldaps = {}
  JSS::LDAPServer.all.each { |svr| @@all_ldaps[svr[:name]] = JSS::LDAPServer.new(:id =>svr[:id])}

  @@all_ldaps
end

.group_in_ldap?(group) ⇒ 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?



123
124
125
126
127
# File 'lib/jss-api/api_object/ldap_server.rb', line 123

def self.group_in_ldap? (group)
  gotgroup = false
  self.all_ldaps.values.each{|ldap| gotgroup =  true unless ldap.find_group(group, :exact).empty? }
  return gotgroup
end

.user_in_ldap?(user) ⇒ 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?



112
113
114
115
116
# File 'lib/jss-api/api_object/ldap_server.rb', line 112

def self.user_in_ldap? (user)
  gotuser = false
  self.all_ldaps.values.each{|ldap| gotuser =  true unless ldap.find_user(user, :exact).empty? }
  return gotuser
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:



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
482
483
484
# File 'lib/jss-api/api_object/ldap_server.rb', line 449

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



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

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:



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
435
436
437
# File 'lib/jss-api/api_object/ldap_server.rb', line 392

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:



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
378
379
380
# File 'lib/jss-api/api_object/ldap_server.rb', line 334

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