lib/activeldap/base.rb in ruby-activeldap-debug-0.5.7 vs lib/activeldap/base.rb in ruby-activeldap-debug-0.5.8
- old
+ new
@@ -173,17 +173,19 @@
# :host overrides the configuration.rb @@host setting with the LDAP server hostname
# :port overrides the configuration.rb @@port setting for the LDAP server port
# :base overwrites Base.base - this affects EVERYTHING
# :try_sasl indicates that a SASL bind should be attempted when binding to the server (default: false)
# :allow_anonymous indicates that a true anonymous bind is allowed when trying to bind to the server (default: true)
+ # :retries - indicates the number of attempts to reconnect that will be undertaken when a stale connection occurs.
def Base.connect(config={}) # :user, :password_block, :logger
# Process config
# Class options
## These will be replace by configuration.rb defaults if defined
@@config = {}
@@config[:host] = config[:host] || @@host
@@config[:port] = config[:port] || @@port
+ @@config[:retries] = config[:retries] || 3
if config[:base]
Base.class_eval <<-"end_eval"
def Base.base
'#{config[:base]}'
end
@@ -212,35 +214,12 @@
@@config[:password_block] = config[:password_block] if config.has_key? :password_block
# Setup bind credentials
@@config[:user] = ENV['USER'] unless @@config[:user]
- # Connect to LDAP
- begin
- # SSL using START_TLS
- @@conn = LDAP::SSLConn.new(@@config[:host], @@config[:port], true)
- rescue
- @@logger.warn "Warning: Failed to connect using TLS!"
- begin
- @@logger.warn "Warning: Attempting SSL connection . . ."
- @@conn = LDAP::SSLConn.new(@@config[:host], @@config[:port], false)
- # HACK: Load the schema here because otherwise you can't tell if the
- # HACK: SSLConn is a real SSL connection.
- @@schema = @@conn.schema() if @@schema.nil?
- rescue
- @@logger.warn "Warning: Attempting unencrypted connection . . ."
- @@conn = LDAP::Conn.new(@@config[:host], @@config[:port])
- end
- end
- @@logger.debug "Connected to #{@@config[:host]}:#{@@config[:port]}."
-
- # Enforce LDAPv3
- @@conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
-
- # Authenticate
- do_bind
-
+ do_connect()
+
# Load Schema (if not straight SSL...)
begin
@@schema = @@conn.schema() if @@schema.nil?
rescue => detail
raise ConnectionError, "#{detail.exception} - LDAP connection failure, or server does not support schema queries."
@@ -252,11 +231,15 @@
# Base.close
# This method deletes the LDAP connection object.
# This does NOT reset any overridden values from a Base.connect call.
def Base.close
- @@conn.unbind unless @@conn.nil?
+ begin
+ @@conn.unbind unless @@conn.nil?
+ rescue
+ # Doesn't matter.
+ end
@@conn = nil
# Make sure it is cleaned up
ObjectSpace.garbage_collect
end
@@ -294,10 +277,11 @@
config[:base] = base() unless config.has_key? :base
values = []
config[:attrs] = config[:attrs].to_a # just in case
+ tries = 0
begin
@@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|
@@ -306,12 +290,20 @@
end
end
values.push(res)
end
rescue RuntimeError => detail
- @@logger.debug "No matches for #{config[:filter]} and attrs #{config[:attrs]}"
- # Do nothing on failure
+ #TODO# Check for 'No message' when retrying
+ # The connection may have gone stale. Let's reconnect and retry.
+ if tries > @max_retries
+ # Do nothing on failure
+ @@logger.debug "No matches for #{config[:filter]} and attrs #{config[:attrs]}"
+ end
+ tries += 1
+ # Reconnect and rebind.
+ do_connect()
+ retry
end
return values
end
# find
@@ -349,10 +341,11 @@
objects = config[:objects]
end
matches = []
+ tries = 0
begin
# Get some attributes
@@conn.search(base(), LDAP::LDAP_SCOPE_SUBTREE, "(#{attr}=#{val})") do |m|
# Extract the dnattr value
dnval = m.dn.split(/,/)[0].split(/=/)[1]
@@ -362,12 +355,20 @@
else
return dnval
end
end
rescue RuntimeError => detail
- @@logger.debug "No matches for #{attr}=#{val}"
- # Do nothing on failure
+ #todo# check for 'no message' when retrying
+ # the connection may have gone stale. let's reconnect and retry.
+ if tries > @max_retries
+ # do nothing on failure
+ @@logger.debug "no matches for #{attr}=#{val}"
+ end
+ tries += 1
+ # reconnect and rebind.
+ do_connect()
+ retry
end
return nil
end
private_class_method :find
@@ -401,10 +402,11 @@
objects = config[:objects]
end
matches = []
+ tries = 0
begin
# Get some attributes
@@conn.search(base(), LDAP::LDAP_SCOPE_SUBTREE, "(#{attr}=#{val})") do |m|
# Extract the dnattr value
dnval = m.dn.split(/,/)[0].split(/=/)[1]
@@ -414,13 +416,20 @@
else
matches.push(dnval)
end
end
rescue RuntimeError => detail
- #p @@conn.err2string(@@conn.err)
- @@logger.debug "No matches for #{attr}=#{val}"
- # Do nothing on failure
+ #todo# check for 'no message' when retrying
+ # the connection may have gone stale. let's reconnect and retry.
+ if tries > @max_retries
+ # do nothing on failure
+ @@logger.debug "no matches for #{attr}=#{val}"
+ end
+ tries += 1
+ # reconnect and rebind.
+ do_connect()
+ retry
end
return matches
end
private_class_method :find_all
@@ -497,10 +506,11 @@
end
@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 = nil # for use in other methods for "caching"
# Break val apart if it is a dn
if val.match(/^#{dnattr()}=([^,=]+),#{base()}$/i)
val = $1
elsif val.match(/[=,]/)
@@ -514,10 +524,11 @@
# Setup what should eb authoritative
@dn = "#{dnattr()}=#{val},#{base()}"
send(:apply_objectclass, required_classes())
else # do a search then
# Search for the existing entry
+ tries = 0
begin
# Get some attributes
Base.connection.search("#{dnattr()}=#{val},#{base()}", LDAP::LDAP_SCOPE_SUBTREE, "objectClass=*") do |m|
# Save DN
@dn = m.dn
@@ -544,11 +555,28 @@
# Populate real data now that we have the schema with aliases
@ldap_data.each do |pair|
send(:attribute_method=, pair[0], pair[1].dup)
end
+ rescue RuntimeError => detail
+ #todo# check for 'no message' when retrying
+ # the connection may have gone stale. let's reconnect and retry.
+ if tries > @max_retries
+ @exists = false
+ # Create what should be the authoritative DN
+ @dn = "#{dnattr()}=#{val},#{base()}"
+ send(:apply_objectclass, required_classes())
+ # Setup dn attribute (later rdn too!)
+ attr_sym = "#{dnattr()}=".to_sym
+ @@logger.debug("new: setting dnattr: #{dnattr()} = #{val}")
+ send(attr_sym, val)
+ end
+ tries += 1
+ # reconnect and rebind.
+ do_connect()
+ retry
rescue LDAP::ResultError
@exists = false
# Create what should be the authoritative DN
@dn = "#{dnattr()}=#{val},#{base()}"
send(:apply_objectclass, required_classes())
@@ -638,13 +666,24 @@
# delete
#
# Delete this entry from LDAP
def delete
@@logger.debug("stub: delete called")
+ tries = 0
begin
@@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.
+ if tries > @max_retries
+ raise DeleteError, "Failed to delete LDAP entry: '#{@dn}'"
+ end
+ tries += 1
+ # reconnect and rebind.
+ do_connect()
+ retry
rescue LDAP::ResultError => detail
raise DeleteError, "Failed to delete LDAP entry: '#{@dn}'"
end
end
@@ -667,11 +706,11 @@
# Expand subtypes to real ldap_data entries
# We can't reuse @ldap_data because an exception would leave
# an object in an unknown state
@@logger.debug("#write: dup'ing @ldap_data")
- ldap_data = @ldap_data.dup
+ ldap_data = Marshal.load(Marshal.dump(@ldap_data))
@@logger.debug("#write: dup finished @ldap_data")
@@logger.debug("#write: expanding subtypes in @ldap_data")
ldap_data.keys.each do |key|
ldap_data[key].each do |value|
if value.class == Hash
@@ -687,11 +726,11 @@
end
@@logger.debug("#write: subtypes expanded for @ldap_data")
# Expand subtypes to real data entries, but leave @data alone
@@logger.debug("#write: dup'ing @data")
- data = @data.dup
+ data = Marshal.load(Marshal.dump(@data))
@@logger.debug("#write: finished dup'ing @data")
@@logger.debug("#write: expanding subtypes for @data")
data.keys.each do |key|
data[key].each do |value|
if value.class == Hash
@@ -766,14 +805,25 @@
# 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")
+ tries = 0
begin
@@logger.debug("#write: modifying #{@dn}")
@@conn.modify(@dn, entry)
@@logger.debug("#write: modify successful")
+ rescue RuntimeError => detail
+ #todo# check for 'no message' when retrying
+ # the connection may have gone stale. let's reconnect and retry.
+ if tries > @max_retries
+ raise WriteError, "Could not update LDAP entry: #{detail}"
+ end
+ tries += 1
+ # reconnect and rebind.
+ do_connect()
+ retry
rescue => detail
raise WriteError, "Could not update LDAP entry: #{detail}"
end
else # add everything!
@@logger.debug("#write: adding all attribute value pairs")
@@ -793,21 +843,31 @@
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
+ tries = 0
begin
@@logger.debug("#write: adding #{@dn}")
@@conn.add(@dn, entry)
@@logger.debug("#write: add successful")
@exists = true
+ rescue RuntimeError => e
+ # The connection may have gone stale. Let's reconnect and retry.
+ if tries > @max_retries
+ raise WriteError, "Could not add LDAP entry[#{Base.connection.err2string(Base.connection.err)}]: #{detail}"
+ end
+ tries += 1
+ # Reconnect and rebind.
+ do_connect()
+ retry
rescue LDAP::ResultError => detail
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 = @data.dup
+ @ldap_data = Marshal.load(Marshal.dump(@data))
@@logger.debug("#write: @ldap_data reset complete")
@@logger.debug("stub: write exitted")
end
@@ -934,20 +994,20 @@
@data['objectClass'] = new_oc.uniq
# Build |data| from schema
# clear attr_method mapping first
@attr_methods = {}
- @must = []
- @may = []
+ @must = []
+ @may = []
new_oc.each do |objc|
# get all attributes for the class
attributes = Base.schema.class_attributes(objc.to_s)
- @must += attributes[:must]
- @may += attributes[:may]
+ @must += attributes[:must]
+ @may += attributes[:may]
end
- @must.uniq!
- @may.uniq!
+ @must.uniq!
+ @may.uniq!
(@must+@may).each do |attr|
# Update attr_method with appropriate
define_attribute_methods(attr)
end
@@ -1049,9 +1109,39 @@
subtype += subsubtype
end
ret_val = [ret_val] unless ret_val.class == Array
return subtype, ret_val
end
+
+
+ # Performs the actually connection. This separate so that it may
+ # be called to refresh stale connections.
+ def Base.do_connect()
+ # Connect to LDAP
+ begin
+ # SSL using START_TLS
+ @@conn = LDAP::SSLConn.new(@@config[:host], @@config[:port], true)
+ rescue
+ @@logger.warn "Warning: Failed to connect using TLS!"
+ begin
+ @@logger.warn "Warning: Attempting SSL connection . . ."
+ @@conn = LDAP::SSLConn.new(@@config[:host], @@config[:port], false)
+ # HACK: Load the schema here because otherwise you can't tell if the
+ # HACK: SSLConn is a real SSL connection.
+ @@schema = @@conn.schema() if @@schema.nil?
+ rescue
+ @@logger.warn "Warning: Attempting unencrypted connection . . ."
+ @@conn = LDAP::Conn.new(@@config[:host], @@config[:port])
+ end
+ end
+ @@logger.debug "Connected to #{@@config[:host]}:#{@@config[:port]}."
+
+ # Enforce LDAPv3
+ @@conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
+
+ # Authenticate
+ do_bind
+ end
# Wrapper all bind activity
def Base.do_bind()
bind_dn = @@config[:bind_format] % [@@config[:user]]