lib/activeldap/base.rb in ruby-activeldap-debug-0.7.1 vs lib/activeldap/base.rb in ruby-activeldap-debug-0.7.2
- old
+ new
@@ -38,11 +38,11 @@
# OO-interface to LDAP assuming pam/nss_ldap-style organization with Active specifics
# Each subclass does a ldapsearch for the matching entry.
# If no exact match, raise an error.
# If match, change all LDAP attributes in accessor attributes on the object.
# -- these are ACTUALLY populated from schema - see subschema.rb example
- # -- @conn.schema().each{|k,vs| vs.each{|v| print("#{k}: #{v}\n")}}
+ # -- @conn.schema2().each{|k,vs| vs.each{|v| print("#{k}: #{v}\n")}}
# -- extract objectClasses from match and populate
# Multiple entries become lists.
# If this isn't read-only then lists become multiple entries, etc.
# AttributeEmpty
@@ -92,10 +92,15 @@
# An exception raised when there is an issue assigning a value to
# an attribute
class AttributeAssignmentError < RuntimeError
end
+ # TimeoutError
+ #
+ # An exception raised when a connection action fails due to a timeout
+ class TimeoutError < RuntimeError
+ end
# Base
#
# Base is the primary class which contains all of the core
# ActiveLDAP functionality. It is meant to only ever be subclassed
@@ -194,21 +199,30 @@
# :retries - indicates the number of attempts to reconnect that will be undertaken when a stale connection occurs. -1 means infinite.
# :sasl_quiet - if true, sets @sasl_quiet on the Ruby/LDAP connection
# :method - whether to use :ssl, :tls, or :plain (unencrypted)
# :retry_wait - seconds to wait before retrying a connection
# :ldap_scope - dictates how to find objects. ONELEVEL by default to avoid dn_attr collisions across OUs. Think before changing.
+ # :return_objects - indicates whether find/find_all will return objects or just the distinguished name attribute value of the matches
+ # :timeout - time in seconds - defaults to disabled. This CAN interrupt search() requests. Be warned.
+ # :retry_on_timeout - whether to reconnect when timeouts occur. Defaults to true
# See lib/configuration.rb for defaults for each option
def Base.connect(config={})
# Process config
# Class options
## These will be replace by configuration.rb defaults if defined
@@config = DEFAULT_CONFIG.dup
config.keys.each do |key|
- if key == :base
+ case key
+ when :base
# Scrub before inserting
base = config[:base].gsub(/['}{#]/, '')
Base.class_eval("def Base.base();'#{base}';end")
+ when :ldap_scope
+ if config[:ldap_scope].class != Fixnum
+ raise ConfigurationError, ':ldap_scope must be a Fixnum'
+ end
+ Base.class_eval("def Base.ldap_scope();#{config[:ldap_scope]};end")
else
@@config[key] = config[key]
end
end
# Assign a easier name for the logger
@@ -240,62 +254,130 @@
rescue
# Doesn't matter.
end
@@conn = nil
# Make sure it is cleaned up
- ObjectSpace.garbage_collect
+ # This causes Ruby/LDAP memory corruption.
+ # ObjectSpace.garbage_collect
end
# Return the LDAP connection object currently in use
- def Base.connection
+ # Alternately execute a command against the connection
+ # object "safely" using a given block. Use the given
+ # "errmsg" for any error conditions.
+ def Base.connection(exc=RuntimeError.new('unknown error'), try_reconnect = true)
+ # Block was given! Let's safely provide access.
+ if block_given?
+ begin
+ Timeout.alarm(@@config[:timeout]) do
+ begin
+ yield @@conn
+ rescue => e
+ # Raise an LDAP error instead of RuntimeError or whatever
+ @@logger.debug("Converting #{e} to useful exception")
+ raise *LDAP::err2exception(@@conn.err) if @@conn.err != 0
+ # Else reraise
+ @@logger.debug('Reraising')
+ raise e
+ end
+ end
+ rescue Timeout::Error => e
+ @@logger.error('Requested action timed out.')
+ retry if try_reconnect and @@config[:retry_on_timeout] and Base.reconnect()
+ message = e.message
+ message = exc.message unless exc.nil?
+ @@logger.error(message)
+ raise TimeoutError, message
+ rescue RuntimeError => e
+ @@logger.error("#{e.class} exception occurred in connection block")
+ @@logger.error("Exception message: #{e.message}")
+ @@logger.error("Exception backtrace: #{e.backtrace}")
+ @@logger.error(exc.message) unless exc.nil?
+ retry if try_reconnect and Base.reconnect()
+ raise exc unless exc.nil?
+ return nil
+ rescue LDAP::ServerDown => e
+ @@logger.error("#{e.class} exception occurred in connection block")
+ @@logger.error("Exception message: #{e.message}")
+ @@logger.error("Exception backtrace: #{e.backtrace}")
+ @@logger.error(exc.message) unless exc.nil?
+ retry if try_reconnect and Base.reconnect()
+ raise exc unless exc.nil?
+ return nil
+ rescue LDAP::ResultError => e
+ @@logger.error("#{e.class} exception occurred in connection block")
+ @@logger.error("Exception message: #{e.message}")
+ @@logger.error("Exception backtrace: #{e.backtrace}")
+ @@logger.error(exc.message) unless exc.nil?
+ retry if try_reconnect and Base.reconnect()
+ raise exc unless exc.nil?
+ return nil
+ rescue LDAP::UndefinedType => e
+ @@logger.error("#{e.class} exception occurred in connection block")
+ @@logger.error("Exception message: #{e.message}")
+ @@logger.error("Exception backtrace: #{e.backtrace}")
+ # Do not retry - not a connection error
+ raise exc unless exc.nil?
+ return nil
+ # Catch all - to be remedied later
+ rescue => e
+ @@logger.error("#{e.class} exception occurred in connection block")
+ @@logger.error("Exception message: #{e.message}")
+ @@logger.error("Exception backtrace: #{e.backtrace}")
+ @@logger.error("Error in catch all: please send debug log to ActiveLDAP author")
+ @@logger.error(exc.message) unless exc.nil?
+ raise exc unless exc.nil?
+ return nil
+ end
+ end
return @@conn
end
# Set the LDAP connection avoiding Base.connect or multiplexing connections
def Base.connection=(conn)
@@conn = conn
end
+ # Determine if we have exceed the retry limit or not.
+ # True is reconnecting is allowed - False if not.
+ def Base.can_reconnect?
+ # Allow connect if we've never connected.
+ return true unless @@config
+ if @@reconnect_attempts < (@@config[:retries] - 1) or
+ @@config[:retries] < 0
+ return true
+ end
+ return false
+ end
+
# Attempts to reconnect up to the number of times allowed
# If forced, try once then fail with ConnectionError if not connected.
def Base.reconnect(force=false)
+ unless @@config
+ @@logger.error('Ignoring force: Base.reconnect called before Base.connect') if force
+ @@logger.debug('Base.reconnect called before Base.connect - calling Base.connect')
+ Base.connect
+ return true
+ end
not_connected = true
while not_connected
- # Just to be clean, unbind if possible
- begin
- @@conn.unbind() if not @@conn.nil? and @@conn.bound?
- rescue
- # Ignore complaints.
- end
- @@conn = nil
-
- if @@config[:retries] == -1 or force == true
+ if Base.can_reconnect?
@@logger.debug('Attempting to reconnect')
+ Base.close()
# Reset the attempts if this was forced.
- @@reconnect_attempts = 0 if @@reconnect_attempts != 0
+ @@reconnect_attempts = 0 if force
+ @@reconnect_attempts += 1 if @@config[:retries] >= 0
begin
do_connect()
not_connected = false
rescue => detail
@@logger.error("Reconnect to server failed: #{detail.exception}")
@@logger.error("Reconnect to server failed backtrace: #{detail.backtrace}")
# Do not loop if forced
raise ConnectionError, detail.message if force
end
- elsif @@reconnect_attempts < @@config[:retries]
- @@logger.debug('Attempting to reconnect')
- @@reconnect_attempts += 1
- begin
- do_connect()
- not_connected = false
- # Reset to 0 if a connection was made.
- @@reconnect_attempts = 0
- rescue => detail
- @@logger.error("Reconnect to server failed: #{detail.exception}")
- @@logger.error("Reconnect to server failed backtrace: #{detail.backtrace}")
- end
else
# Raise a warning
raise ConnectionError, 'Giving up trying to reconnect to LDAP server.'
end
@@ -313,49 +395,35 @@
# search
#
# Wraps Ruby/LDAP connection.search to make it easier to search for specific
# data without cracking open Base.connection
def Base.search(config={})
- unless Base.connection
- if @@config
- ActiveLDAP::Base.connect(@@config)
- else
- ActiveLDAP::Base.connect
- end
- end
+ Base.reconnect if Base.connection.nil? and Base.can_reconnect?
config[:filter] = 'objectClass=*' unless config.has_key? :filter
config[:attrs] = [] unless config.has_key? :attrs
config[:scope] = LDAP::LDAP_SCOPE_SUBTREE unless config.has_key? :scope
config[:base] = base() unless config.has_key? :base
values = []
config[:attrs] = config[:attrs].to_a # just in case
- begin
- @@conn.search(config[:base], config[:scope], config[:filter], config[:attrs]) do |m|
+ result = Base.connection() do |conn|
+ conn.search(config[:base], config[:scope], config[:filter], config[:attrs]) do |m|
res = {}
res['dn'] = [m.dn.dup] # For consistency with the below
m.attrs.each do |attr|
if config[:attrs].member? attr or config[:attrs].empty?
res[attr] = m.vals(attr).dup
end
end
values.push(res)
end
- rescue RuntimeError => detail
- #TODO# Check for 'No message' when retrying
- # The connection may have gone stale. Let's reconnect and retry.
- retry if Base.reconnect()
+ end
+ if result.nil?
# Do nothing on failure
@@logger.debug "No matches for #{config[:filter]} and attrs #{config[:attrs]}"
- rescue => detail
- if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
- @@logger.debug("Failed to write: #{entry}")
- retry if Base.reconnect()
- end
- raise detail
end
return values
end
# find
@@ -363,18 +431,12 @@
# Finds the first match for value where |value| is the value of some
# |field|, or the wildcard match. This is only useful for derived classes.
# usage: Subclass.find(:attribute => "cn", :value => "some*val", :objects => true)
# Subclass.find('some*val')
#
- def Base.find(config = {})
- unless Base.connection
- if @@config
- ActiveLDAP::Base.connect(@@config)
- else
- ActiveLDAP::Base.connect
- end
- end
+ def Base.find(config='*')
+ Base.reconnect if Base.connection.nil? and Base.can_reconnect?
if self.class == Class
klass = self.ancestors[0].to_s.split(':').last
real_klass = self.ancestors[0]
else
@@ -382,109 +444,74 @@
real_klass = self.class
end
# Allow a single string argument
attr = dnattr()
- objects = false
+ objects = @@config[:return_objects]
val = config
# Or a hash
- if config.respond_to?"has_key?"
+ if config.respond_to?(:has_key?)
attr = config[:attribute] || dnattr()
val = config[:value] || '*'
- objects = config[:objects]
+ objects = config[:objects] || @@config[:return_objects]
end
- matches = []
-
- begin
+ Base.connection(ConnectionError.new("Failed in #{self.class}#find(#{config.inspect})")) do |conn|
# Get some attributes
- @@conn.search(base(), @@config[:ldap_scope], "(#{attr}=#{val})") do |m|
+ conn.search(base(), ldap_scope(), "(#{attr}=#{val})") do |m|
# Extract the dnattr value
dnval = m.dn.split(/,/)[0].split(/=/)[1]
if objects
return real_klass.new(m)
else
return dnval
end
end
- rescue RuntimeError => detail
- #TODO# Check for 'No message' when retrying
- # The connection may have gone stale. Let's reconnect and retry.
- retry if Base.reconnect()
-
- # Do nothing on failure
- @@logger.debug "no matches for #{attr}=#{val}"
- rescue => detail
- if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
- @@logger.debug("Failed to write: #{entry}")
- retry if Base.reconnect()
- end
- raise detail
end
+ # If we're here, there were no results
return nil
end
private_class_method :find
# find_all
#
# Finds all matches for value where |value| is the value of some
# |field|, or the wildcard match. This is only useful for derived classes.
- def Base.find_all(config = {})
- unless Base.connection
- if @@config
- ActiveLDAP::Base.connect(@@config)
- else
- ActiveLDAP::Base.connect
- end
- end
+ def Base.find_all(config='*')
+ Base.reconnect if Base.connection.nil? and Base.can_reconnect?
if self.class == Class
real_klass = self.ancestors[0]
else
real_klass = self.class
end
# Allow a single string argument
val = config
attr = dnattr()
- objects = false
+ objects = @@config[:return_objects]
# Or a hash
- if config.respond_to?"has_key?"
+ if config.respond_to?(:has_key?)
val = config[:value] || '*'
attr = config[:attribute] || dnattr()
- objects = config[:objects]
+ objects = config[:objects] || @@config[:return_objects]
end
matches = []
-
- begin
+ Base.connection(ConnectionError.new("Failed in #{self.class}#find_all(#{config.inspect})")) do |conn|
# Get some attributes
- @@conn.search(base(), @@config[:ldap_scope],
- "(#{attr}=#{val})") do |m|
+ conn.search(base(), ldap_scope(), "(#{attr}=#{val})") do |m|
# Extract the dnattr value
dnval = m.dn.split(/,/)[0].split(/=/)[1]
if objects
matches.push(real_klass.new(m))
else
matches.push(dnval)
end
end
- rescue RuntimeError => detail
- #TODO# Check for 'No message' when retrying
- # The connection may have gone stale. Let's reconnect and retry.
- retry if Base.reconnect()
-
- # Do nothing on failure
- @@logger.debug "no matches for #{attr}=#{val}"
- rescue => detail
- if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
- @@logger.debug("Failed to write: #{entry}")
- retry if Base.reconnect()
- end
- raise detail
end
return matches
end
private_class_method :find_all
@@ -497,11 +524,11 @@
# ldap server such as 'dc=example,dc=com', and
# it should be overwritten by including
# configuration.rb into this class.
# When subclassing, the specified prefix will be concatenated.
def Base.base
- 'dc=example,dc=com'
+ 'dc=localdomain'
end
# Base.dnattr
#
# This is a placeholder for the class method that will
@@ -524,12 +551,23 @@
# arrays to occurs.
def Base.required_classes
[]
end
+ # Base.ldap_scope
+ #
+ # This method when included into Base provides
+ # an inheritable, overwritable configuration setting
+ #
+ # This value should be the default LDAP scope behavior
+ # desired.
+ def Base.ldap_scope
+ LDAP::LDAP_SCOPE_ONELEVEL
+ end
+
### All instance methods, etc
# new
#
# Creates a new instance of Base initializing all class and all
@@ -537,24 +575,16 @@
# exist for dnattr, the first one put here will be authoritative
# TODO: Add # support for relative distinguished names
# val can be a dn attribute value, a full DN, or a LDAP::Entry. The use
# with a LDAP::Entry is primarily meant for internal use by find and
# find_all.
+ #
def initialize(val)
@exists = false
- # Try a default connection if none made explicitly
- if Base.connection.nil? and @@reconnect_attempts < @@config[:retries]
- # Use @@config if it has been prepopulated and the conn is down.
- if @@config
- ActiveLDAP::Base.reconnect
- else
- ActiveLDAP::Base.connect
- end
- elsif Base.connection.nil?
- @@logger.error('Attempted to initialize a new object with no connection')
- raise ConnectionError, 'Number of reconnect attempts exceeded.'
- end
+ # Make sure we're connected
+ Base.reconnect if Base.connection.nil? and Base.can_reconnect?
+
if val.class == LDAP::Entry
# Call import, which is basically initialize
# without accessing LDAP.
@@logger.debug "initialize: val is a LDAP::Entry - running import."
import(val)
@@ -567,15 +597,15 @@
@data = {} # where the r/w entry data is stored
@ldap_data = {} # original ldap entry data
@attr_methods = {} # list of valid method calls for attributes used for dereferencing
@last_oc = false # for use in other methods for "caching"
if dnattr().empty?
- raise RuntimeError, "dnattr() not set for this class."
+ raise ConfigurationError, "dnattr() not set for this class: #{self.class}"
end
- # Break val apart if it is a dn
- if val.match(/^#{dnattr()}=([^,=]+),#{base()}$/i)
+ # Extract dnattr if val looks like a dn
+ if val.match(/^#{dnattr()}=([^,=]+),/i)
val = $1
elsif val.match(/[=,]/)
@@logger.info "initialize: Changing val from '#{val}' to '' because it doesn't match the DN."
val = ''
end
@@ -587,21 +617,21 @@
else
# Create what should be the authoritative DN
@dn = "#{dnattr()}=#{val},#{base()}"
# Search for the existing entry
- begin
+ Base.connection(ConnectionError.new("Failed in #{self.class}#new(#{val.inspect})")) do |conn|
# Get some attributes
- Base.connection.search(base(), @@config[:ldap_scope], "(#{dnattr()}=#{val})") do |m|
+ conn.search(base(), ldap_scope(), "(#{dnattr()}=#{val})") do |m|
@exists = true
# Save DN
@dn = m.dn
# Load up data into tmp
@@logger.debug("loading entry: #{@dn}")
m.attrs.each do |attr|
# Load with subtypes just like @data
- @@logger.debug("calling make_subtypes for m.vals(attr).dup")
+ @@logger.debug('calling make_subtypes for m.vals(attr).dup')
safe_attr, value = make_subtypes(attr, m.vals(attr).dup)
@@logger.debug("finished make_subtypes for #{attr}")
# Add subtype to any existing values
if @ldap_data.has_key? safe_attr
value.each do |v|
@@ -610,31 +640,37 @@
else
@ldap_data[safe_attr] = value
end
end
end
- rescue RuntimeError => detail
- #TODO# Check for 'No message' when retrying
- # The connection may have gone stale. Let's reconnect and retry.
- retry if Base.reconnect()
-
- # Do nothing on failure
- @@logger.error('new: unable to search for entry')
- raise detail
- rescue LDAP::ResultError
end
end
# Do the actual object setup work.
if @exists
+ # Make sure the server uses objectClass and not objectclass
+ unless @ldap_data.has_key?('objectClass')
+ real_objc = @ldap_data.grep(/^objectclass$/i)
+ if real_objc.size == 1
+ @ldap_data['objectClass'] = @ldap_data[real_objc]
+ @ldap_data.delete(real_objc)
+ else
+ raise AttributeEmpty, 'objectClass was not sent by LDAP server!'
+ end
+ end
+
# Populate schema data
send(:apply_objectclass, @ldap_data['objectClass'])
# Populate real data now that we have the schema with aliases
@ldap_data.each do |pair|
real_attr = @attr_methods[pair[0]]
@@logger.debug("new: #{pair[0].inspect} method maps to #{real_attr}")
+ if real_attr.nil?
+ @@logger.error("Unable to resolve attribute value #{pair[0].inspect}. " +
+ "Unpredictable behavior likely!")
+ end
@data[real_attr] = pair[1].dup
@@logger.debug("new: #{real_attr} set to #{pair[1]}")
end
else
send(:apply_objectclass, required_classes())
@@ -654,11 +690,11 @@
# Return attribute methods so that a program can determine available
# attributes dynamically without schema awareness
def attributes
@@logger.debug("stub: attributes called")
send(:apply_objectclass, @data['objectClass']) if @data['objectClass'] != @last_oc
- return @attr_methods.keys
+ return @attr_methods.keys.map {|x|x.downcase}.uniq
end
# exists?
#
# Return whether the entry exists in LDAP or not
@@ -704,11 +740,12 @@
end
# Make sure all MUST attributes have a value
@data['objectClass'].each do |objc|
@must.each do |req_attr|
- deref = @attr_methods[req_attr]
+ # Downcase to ensure we catch schema problems
+ deref = @attr_methods[req_attr.downcase]
# Set default if it wasn't yet set.
@data[deref] = [] if @data[deref].nil?
# Check for missing requirements.
if @data[deref].empty?
raise AttributeEmpty,
@@ -722,24 +759,14 @@
# delete
#
# Delete this entry from LDAP
def delete
@@logger.debug("stub: delete called")
- begin
- @@conn.delete(@dn)
+ Base.connection(DeleteError.new(
+ "Failed to delete LDAP entry: '#{@dn}'")) do |conn|
+ conn.delete(@dn)
@exists = false
- rescue RuntimeError => detail
- #todo# check for 'no message' when retrying
- # the connection may have gone stale. let's reconnect and retry.
- retry if Base.reconnect()
- raise DeleteError, "Failed to delete LDAP entry: '#{@dn}'"
- rescue LDAP::ResultError => detail
- if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
- @@logger.debug("Failed to write: #{entry}")
- retry if Base.reconnect()
- end
- raise DeleteError, "Failed to delete LDAP entry: '#{@dn}'"
end
end
# write
@@ -776,17 +803,26 @@
end
ldap_data[key].delete(value)
end
end
end
- @@logger.debug("#write: subtypes expanded for @ldap_data")
+ @@logger.debug('#write: subtypes expanded for @ldap_data')
# Expand subtypes to real data entries, but leave @data alone
- @@logger.debug("#write: dup'ing @data")
+ @@logger.debug('#write: duping @data')
data = Marshal.load(Marshal.dump(@data))
- @@logger.debug("#write: finished dup'ing @data")
- @@logger.debug("#write: expanding subtypes for @data")
+ @@logger.debug('#write: finished duping @data')
+
+ @@logger.debug('#write: removing disallowed attributes from @data')
+ bad_attrs = @data.keys - (@must+@may)
+ bad_attrs.each do |removeme|
+ data.delete(removeme)
+ end
+ @@logger.debug('#write: finished removing disallowed attributes from @data')
+
+
+ @@logger.debug('#write: expanding subtypes for @data')
data.keys.each do |key|
data[key].each do |value|
if value.class == Hash
suffix, real_value = extract_subtypes(value)
if data.has_key? key + suffix
@@ -796,22 +832,21 @@
end
data[key].delete(value)
end
end
end
- @@logger.debug("#write: subtypes expanded for @data")
+ @@logger.debug('#write: subtypes expanded for @data')
-
if @exists
# Cycle through all attrs to determine action
action = {}
replaceable = []
# Now that all the subtypes will be treated as unique attributes
# we can see what's changed and add anything that is brand-spankin'
# new.
- @@logger.debug("#write: traversing ldap_data determining replaces and deletes")
+ @@logger.debug('#write: traversing ldap_data determining replaces and deletes')
ldap_data.each do |pair|
suffix = ''
binary = 0
name, *suffix_a = pair[0].split(/;/)
@@ -839,12 +874,12 @@
@@logger.debug("removing attribute from existing entry: #{name+suffix}")
entry.push(LDAP.mod(LDAP::LDAP_MOD_REPLACE|binary, name + suffix, []))
end
end
end
- @@logger.debug("#write: finished traversing ldap_data")
- @@logger.debug("#write: traversing data determining adds")
+ @@logger.debug('#write: finished traversing ldap_data')
+ @@logger.debug('#write: traversing data determining adds')
data.each do |pair|
suffix = ''
binary = 0
name, *suffix_a = pair[0].split(/;/)
@@ -862,31 +897,19 @@
# REPLACE will function like ADD, but doesn't hit EQUALITY problems
# TODO: Added equality(attr) to Schema2
entry.push(LDAP.mod(LDAP::LDAP_MOD_REPLACE|binary, name + suffix, value)) unless value.empty?
end
end
- @@logger.debug("#write: traversing data complete")
- begin
+ @@logger.debug('#write: traversing data complete')
+ Base.connection(WriteError.new(
+ "Failed to modify: '#{entry}'")) do |conn|
@@logger.debug("#write: modifying #{@dn}")
- @@conn.modify(@dn, entry)
- @@logger.debug("#write: modify successful")
- rescue RuntimeError => detail
- #todo# check for SERVER_DOWN
- # the connection may have gone stale. let's reconnect and retry.
- retry if Base.reconnect()
- raise WriteError, "Could not update LDAP entry: #{detail}"
- rescue => detail
- @@logger.debug(LDAP::err2exception(@@conn.err).inspect)
- if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
- @@logger.debug("Failed to write: #{entry}")
- retry if Base.reconnect()
- end
- @@logger.debug("Failed to write: #{entry}")
- raise WriteError, "Could not update LDAP entry: #{detail}"
+ conn.modify(@dn, entry)
+ @@logger.debug('#write: modify successful')
end
else # add everything!
- @@logger.debug("#write: adding all attribute value pairs")
+ @@logger.debug('#write: adding all attribute value pairs')
@@logger.debug("#write: adding #{@attr_methods[dnattr()].inspect} = #{data[@attr_methods[dnattr()]].inspect}")
entry.push(LDAP.mod(LDAP::LDAP_MOD_ADD, @attr_methods[dnattr()],
data[@attr_methods[dnattr()]]))
@@logger.debug("#write: adding objectClass = #{data[@attr_methods['objectClass']].inspect}")
entry.push(LDAP.mod(LDAP::LDAP_MOD_ADD, 'objectClass',
@@ -901,34 +924,30 @@
end
@@logger.debug("adding attribute to new entry: #{pair[0].inspect}: #{pair[1].inspect}")
entry.push(LDAP.mod(LDAP::LDAP_MOD_ADD|binary, pair[0], pair[1]))
end
end
- begin
+ Base.connection(WriteError.new(
+ "Failed to add: '#{entry}'")) do |conn|
@@logger.debug("#write: adding #{@dn}")
- @@conn.add(@dn, entry)
+ conn.add(@dn, entry)
@@logger.debug("#write: add successful")
@exists = true
- rescue RuntimeError => detail
- # The connection may have gone stale. Let's reconnect and retry.
- retry if Base.reconnect()
- raise WriteError, "Could not add LDAP entry[#{Base.connection.err2string(Base.connection.err)}]: #{detail}"
- rescue LDAP::ResultError => detail
- if LDAP::err2exception(@@conn.err)[0] == LDAP::ServerDown
- @@logger.debug("Failed to write: #{entry}")
- retry if Base.reconnect()
- end
- raise WriteError, "Could not add LDAP entry[#{Base.connection.err2string(Base.connection.err)}]: #{detail}"
end
end
@@logger.debug("#write: resetting @ldap_data to a dup of @data")
- @ldap_data = Marshal.load(Marshal.dump(@data))
- @@logger.debug("#write: @ldap_data reset complete")
- @@logger.debug("stub: write exitted")
+ @ldap_data = Marshal.load(Marshal.dump(data))
+ # Delete items disallowed by objectclasses.
+ # They should have been removed from ldap.
+ @@logger.debug('#write: removing attributes from @ldap_data not sent in data')
+ bad_attrs.each do |removeme|
+ @ldap_data.delete(removeme)
+ end
+ @@logger.debug('#write: @ldap_data reset complete')
+ @@logger.debug('stub: write exitted')
end
-
# method_missing
#
# If a given method matches an attribute or an attribute alias
# then call the appropriate method.
# TODO: Determine if it would be better to define each allowed method
@@ -968,11 +987,11 @@
def methods
return __methods + attributes()
end
- private
+ private
# import(LDAP::Entry)
#
# Overwrites an existing entry (usually called by new)
# with the data given in the data given in LDAP::Entry.
@@ -1070,18 +1089,10 @@
@may.uniq!
(@must+@may).each do |attr|
# Update attr_method with appropriate
define_attribute_methods(attr)
end
-
- # Delete all now innew_ocid attributes given the new objectClasses
- @data.keys.each do |key|
- # If it's not a proper aliased attribute, drop it
- unless @attr_methods.has_key? key
- @data.delete(key)
- end
- end
end
# Enforce typing:
@@ -1196,25 +1207,22 @@
@@logger.error("Failed to connect using #{@@config[:method]}")
raise e
end
# Enforce LDAPv3
- @@conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
+ Base.connection(nil, false) do |conn|
+ conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
+ end
# Authenticate
do_bind
# Retrieve the schema. We need this to automagically determine attributes
- begin
- @@schema = @@conn.schema() if @@schema.nil?
- rescue => e
- @@logger.error("Failed to retrieve the schema (#{@@config[:method]})")
- @@logger.error("Schema failure exception: #{e.exception}")
- @@logger.error("Schema failure backtrace: #{e.backtrace}")
- raise ConnectionError, "#{e.exception} - LDAP connection failure, or server does not support schema queries."
+ exc = ConnectionError.new("Unable to retrieve schema from server (#{@@config[:method]})")
+ Base.connection(exc, false) do |conn|
+ @@schema = @@conn.schema2() if @@schema.nil?
end
-
@@logger.debug "Connected to #{@@config[:host]}:#{@@config[:port]} using #{@@config[:method]}"
end
# Wrapper all bind activity
def Base.do_bind()
@@ -1226,43 +1234,39 @@
if @@config[:try_sasl] and do_sasl_bind(bind_dn)
@@logger.info('Bound SASL')
elsif do_simple_bind(bind_dn)
@@logger.info('Bound simple')
elsif @@config[:allow_anonymous] and do_anonymous_bind(bind_dn)
- @@logger.info('Bound simple')
+ @@logger.info('Bound anonymous')
else
- @@logger.error('Failed to bind using any available method')
raise *LDAP::err2exception(@@conn.err) if @@conn.err != 0
+ raise AuthenticationError, 'All authentication methods exhausted.'
end
return @@conn.bound?
end
+ private_class_method :do_bind
# Base.do_anonymous_bind
#
# Bind to LDAP with the given DN, but with no password. (anonymous!)
def Base.do_anonymous_bind(bind_dn)
@@logger.info "Attempting anonymous authentication"
- begin
- @@conn.bind()
+ Base.connection(nil, false) do |conn|
+ conn.bind()
return true
- rescue => e
- @@logger.debug "LDAP Error: #{@@conn.err2string(@@conn.err)}"
- @@logger.debug "Exception: #{e.exception}"
- @@logger.debug "Backtrace: #{e.backtrace}"
- @@logger.warn "Warning: Anonymous authentication failed."
- @@logger.warn "message: #{e.message}"
- return false
end
+ return false
end
+ private_class_method :do_anonymous_bind
# Base.do_simple_bind
#
# Bind to LDAP with the given DN and password
def Base.do_simple_bind(bind_dn)
# Bail if we have no password or password block
- if not @@config[:password_block].nil? and not @@config[:password].nil?
+ if @@config[:password_block].nil? and @@config[:password].nil?
return false
end
# TODO: Give a warning to reconnect users with password clearing
# Get the passphrase for the first time, or anew if we aren't storing
@@ -1280,64 +1284,69 @@
@@logger.error('Skipping simple bind: ' +
':password_block and :password options are empty.')
return false
end
- begin
- @@conn.bind(bind_dn, password)
- rescue => e
- @@logger.debug "LDAP Error: #{@@conn.err2string(@@conn.err)}"
- # TODO: replace this with LDAP::err2exception()
- if @@conn.err == LDAP::LDAP_SERVER_DOWN
- @@logger.error "Warning: " + e.message
- else
- @@logger.warn "Warning: SIMPLE authentication failed."
- end
- return false
- end
# Store the password for quick reference later
if @@config[:store_password]
@@config[:password] = password
elsif @@config[:store_password] == false
@@config[:password] = nil
end
- return true
+
+ Base.connection(nil, false) do |conn|
+ conn.bind(bind_dn, password)
+ return true
+ end
+ return false
end
+ private_class_method :do_simple_bind
# Base.do_sasl_bind
#
# Bind to LDAP with the given DN using any available SASL methods
def Base.do_sasl_bind(bind_dn)
# Get all SASL mechanisms
- mechanisms = @@conn.root_dse[0]['supportedSASLMechanisms']
+ #
+ mechanisms = []
+ exc = ConnectionError.new('Root DSE query failed')
+ Base.connection(exc, false) do |conn|
+ mechanisms = conn.root_dse[0]['supportedSASLMechanisms']
+ end
# Use GSSAPI if available
# Currently only GSSAPI is supported with Ruby/LDAP from
# http://caliban.org/files/redhat/RPMS/i386/ruby-ldap-0.8.2-4.i386.rpm
# TODO: Investigate further SASL support
if mechanisms.respond_to? :member? and mechanisms.member? 'GSSAPI'
- begin
- @@conn.sasl_quiet = @@config[:sasl_quiet] if @@config.has_key?(:sasl_quiet)
- @@conn.sasl_bind(bind_dn, 'GSSAPI')
+ Base.connection(nil, false) do |conn|
+ conn.sasl_quiet = @@config[:sasl_quiet] if @@config.has_key?(:sasl_quiet)
+ conn.sasl_bind(bind_dn, 'GSSAPI')
return true
- rescue
- @@logger.debug "LDAP Error: #{@@conn.err2string(@@conn.err)}"
- @@logger.warn "Warning: SASL GSSAPI authentication failed."
- return false
end
end
return false
end
+ private_class_method :do_sasl_bind
# base
#
# Returns the value of self.class.base
# This is just syntactic sugar
def base
@@logger.debug("stub: called base")
self.class.base
end
+ # ldap_scope
+ #
+ # Returns the value of self.class.ldap_scope
+ # This is just syntactic sugar
+ def ldap_scope
+ @@logger.debug("stub: called ldap_scope")
+ self.class.ldap_scope
+ end
+
# required_classes
#
# Returns the value of self.class.required_classes
# This is just syntactic sugar
def required_classes
@@ -1417,9 +1426,11 @@
end
aliases = Base.schema.attribute_aliases(attr)
aliases.each do |ali|
@@logger.debug("associating #{ali} --> #{attr}")
@attr_methods[ali] = attr
+ @@logger.debug("associating #{ali.downcase} --> #{attr}")
+ @attr_methods[ali.downcase] = attr
end
@@logger.debug("stub: leaving define_attribute_methods(#{attr.inspect})")
end
# array_of