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]]