Class: ActiveLDAP::Base
- Inherits:
-
Object
- Object
- ActiveLDAP::Base
- Defined in:
- lib/activeldap/base.rb
Overview
Base
Base is the primary class which contains all of the core ActiveLDAP functionality. It is meant to only ever be subclassed by extension classes.
Constant Summary collapse
- @@config =
All class-wide variables
nil
- @@schema =
Container for current connection settings
nil
- @@conn =
LDAP server’s schema
nil
- @@reconnect_attempts =
LDAP connection
0
Instance Attribute Summary collapse
-
#logger ⇒ Object
Returns the value of attribute logger.
-
#may ⇒ Object
readonly
Parsed schema structures.
-
#must ⇒ Object
readonly
Parsed schema structures.
Class Method Summary collapse
-
.base ⇒ Object
Make the return value the string that is your LDAP base.
-
.close ⇒ Object
Base.close This method deletes the LDAP connection object.
-
.connect(config = {}) ⇒ Object
Connect and bind to LDAP creating a class variable for use by all ActiveLDAP objects.
-
.connection ⇒ Object
Return the LDAP connection object currently in use.
-
.connection=(conn) ⇒ Object
Set the LDAP connection avoiding Base.connect or multiplexing connections.
-
.create_object(config = {}) ⇒ Object
Driver generator.
-
.dnattr ⇒ Object
Base.dnattr.
-
.reconnect(force = false) ⇒ Object
Attempts to reconnect up to the number of times allowed If forced, try once then fail with ConnectionError if not connected.
-
.required_classes ⇒ Object
This is optionally set to the array of objectClass names that are minimally required for EVERY object on your LDAP server.
-
.schema ⇒ Object
Return the schema object.
-
.search(config = {}) ⇒ Object
search.
Instance Method Summary collapse
-
#__methods ⇒ Object
Add available attributes to the methods.
-
#attributes ⇒ Object
attributes.
-
#delete ⇒ Object
delete.
-
#dn ⇒ Object
dn.
-
#exists? ⇒ Boolean
exists?.
-
#initialize(val) ⇒ Base
constructor
new.
-
#method_missing(name, *args) ⇒ Object
method_missing.
- #methods ⇒ Object
-
#validate ⇒ Object
validate.
-
#write ⇒ Object
write.
Constructor Details
#initialize(val) ⇒ Base
new
Creates a new instance of Base initializing all class and all initialization. Defines local defaults. See examples If multiple values 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.
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 |
# File 'lib/activeldap/base.rb', line 542 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 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) return end if val.class != String raise TypeError, "Object key must be a String" 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 = false # for use in other methods for "caching" if dnattr().empty? raise RuntimeError, "dnattr() not set for this class." end # Break val apart if it is a dn if val.match(/^#{dnattr()}=([^,=]+),#{base()}$/i) val = $1 elsif val.match(/[=,]/) @@logger.info "initialize: Changing val from '#{val}' to '' because it doesn't match the DN." val = '' end # Do a search - if it exists, pull all data and parse schema, if not, just set the hierarchical data if val.class != String or val.empty? raise TypeError, 'a dn attribute String must be supplied ' + 'on initialization' else # Create what should be the authoritative DN @dn = "#{dnattr()}=#{val},#{base()}" # Search for the existing entry begin # Get some attributes Base.connection.search(base(), @@config[: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") 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| @ldap_data[safe_attr].push(v) end 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 # 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}") @data[real_attr] = pair[1].dup @@logger.debug("new: #{real_attr} set to #{pair[1]}") end else send(:apply_objectclass, required_classes()) # Setup dn attribute (later rdn too!) real_dnattr = @attr_methods[dnattr()] @data[real_dnattr] = val @@logger.debug("new: setting dnattr: #{real_dnattr} = #{val}") end end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args) ⇒ Object
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
using class_eval instead of using method_missing. This would
give tab completion in irb.
937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 |
# File 'lib/activeldap/base.rb', line 937 def method_missing(name, *args) @@logger.debug("stub: called method_missing(#{name.inspect}, #{args.inspect})") # dynamically update the available attributes without requiring an # explicit call. The cache 'last_oc' saves a lot of cpu time. if @data['objectClass'] != @last_oc @@logger.debug("method_missing(#{name.inspect}, #{args.inspect}): updating apply_objectclass(#{@data['objectClass'].inspect})") send(:apply_objectclass, @data['objectClass']) end key = name.to_s case key when /^(\S+)=$/ real_key = $1 @@logger.debug("method_missing: attr_methods has_key? #{real_key}") if @attr_methods.has_key? real_key raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" if args.size != 1 @@logger.debug("method_missing: calling :attribute_method=(#{real_key}, #{args[0]})") return send(:attribute_method=, real_key, args[0]) end else @@logger.debug("method_missing: attr_methods has_key? #{key}") if @attr_methods.has_key? key raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" if args.size > 1 return attribute_method(key, *args) end end raise NoMethodError, "undefined method `#{key}' for #{self}" end |
Instance Attribute Details
#logger ⇒ Object
Returns the value of attribute logger.
106 107 108 |
# File 'lib/activeldap/base.rb', line 106 def logger @logger end |
#may ⇒ Object (readonly)
Parsed schema structures
105 106 107 |
# File 'lib/activeldap/base.rb', line 105 def may @may end |
#must ⇒ Object (readonly)
Parsed schema structures
105 106 107 |
# File 'lib/activeldap/base.rb', line 105 def must @must end |
Class Method Details
.base ⇒ Object
Make the return value the string that is your LDAP base
501 502 503 |
# File 'lib/activeldap/base.rb', line 501 def Base.base 'dc=example,dc=com' end |
.close ⇒ Object
Base.close This method deletes the LDAP connection object. This does NOT reset any overridden values from a Base.connect call.
237 238 239 240 241 242 243 244 245 246 |
# File 'lib/activeldap/base.rb', line 237 def Base.close begin @@conn.unbind unless @@conn.nil? rescue # Doesn't matter. end @@conn = nil # Make sure it is cleaned up ObjectSpace.garbage_collect end |
.connect(config = {}) ⇒ Object
Connect and bind to LDAP creating a class variable for use by all ActiveLDAP objects.
config
config
must be a hash that may contain any of the following fields: :user, :password_block, :logger, :host, :port, :base, :bind_format, :try_sasl, :allow_anonymous :user specifies the username to bind with. :bind_format specifies the string to substitute the username into on bind. e.g. uid=%s,ou=People,dc=dataspill,dc=org. Overrides @@bind_format. :password_block specifies a Proc object that will yield a String to be used as the password when called. :logger specifies a preconfigured Log4r::Logger to be used for all logging :host sets the LDAP server hostname :port sets 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. -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. See lib/configuration.rb for defaults for each option
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/activeldap/base.rb', line 200 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 # Scrub before inserting base = config[:base].gsub(/['}{#]/, '') Base.class_eval("def Base.base();'#{base}';end") else @@config[key] = config[key] end end # Assign a easier name for the logger @@logger = @@config[:logger] || nil # Setup default logger to console if @@logger.nil? @@logger = Log4r::Logger.new('activeldap') @@logger.level = Log4r::OFF Log4r::StderrOutputter.new 'console' @@logger.add('console') end # Reset for the new connection @@reconnect_attempts = 0 # Make the connection. do_connect() # Make irb users happy with a 'true' return true end |
.connection ⇒ Object
Return the LDAP connection object currently in use
249 250 251 |
# File 'lib/activeldap/base.rb', line 249 def Base.connection return @@conn end |
.connection=(conn) ⇒ Object
Set the LDAP connection avoiding Base.connect or multiplexing connections
254 255 256 |
# File 'lib/activeldap/base.rb', line 254 def Base.connection=(conn) @@conn = conn end |
.create_object(config = {}) ⇒ Object
Driver generator
TODO add type checking This let’s you call this method to create top-level extension object. This is really just a proof of concept and has not truly useful purpose. example: Base.create_object(:class => “user”, :dnattr => “uid”, :classes => [‘top’])
THIS METHOD IS DANGEROUS. INPUT IS NOT SANITIZED.
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/activeldap/base.rb', line 122 def Base.create_object(config={}) # Just upcase the first letter of the new class name str = config[:class] class_name = str[0].chr.upcase + str[1..-1] attr = config[:dnattr] # "uid" prefix = config[:base] # "ou=People" # [ 'top', 'posixAccount' ] classes_array = config[:classes] || [] # [ [ :groups, {:class_name => "Group", :foreign_key => "memberUid"}] ] belongs_to_array = config[:belongs_to] || [] # [ [ :members, {:class_name => "User", :foreign_key => "uid", :local_key => "memberUid"}] ] has_many_array = config[:has_many] || [] raise TypeError, ":objectclasses must be an array" unless classes_array.respond_to? :size raise TypeError, ":belongs_to must be an array" unless belongs_to_array.respond_to? :size raise TypeError, ":has_many must be an array" unless has_many_array.respond_to? :size # Build classes array classes = '[' classes_array.map! {|x| x = "'#{x}'"} classes << classes_array.join(', ') classes << ']' # Build belongs_to belongs_to = [] if belongs_to_array.size > 0 belongs_to_array.each do |bt| line = [ "belongs_to :#{bt[0]}" ] bt[1].keys.each do |key| line << ":#{key} => '#{bt[1][key]}'" end belongs_to << line.join(', ') end end # Build has_many has_many = [] if has_many_array.size > 0 has_many_array.each do |hm| line = [ "has_many :#{hm[0]}" ] hm[1].keys.each do |key| line << ":#{key} => '#{hm[1][key]}'" end has_many << line.join(', ') end end self.class.module_eval <<-"end_eval" class ::#{class_name} < ActiveLDAP::Base ldap_mapping :dnattr => "#{attr}", :prefix => "#{prefix}", :classes => #{classes} #{belongs_to.join("\n")} #{has_many.join("\n")} end end_eval end |
.dnattr ⇒ Object
Base.dnattr
This is a placeholder for the class method that will be overridden on calling ldap_mapping in a subclass. Using a class method allows for clean inheritance from classes that already have a ldap_mapping.
511 512 513 |
# File 'lib/activeldap/base.rb', line 511 def Base.dnattr '' end |
.reconnect(force = false) ⇒ Object
Attempts to reconnect up to the number of times allowed If forced, try once then fail with ConnectionError if not connected.
260 261 262 263 264 265 266 267 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 |
# File 'lib/activeldap/base.rb', line 260 def Base.reconnect(force=false) 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 @@logger.debug('Attempting to reconnect') # Reset the attempts if this was forced. @@reconnect_attempts = 0 if @@reconnect_attempts != 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. 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 # Sleep before looping sleep @@config[:retry_wait] end return true end |
.required_classes ⇒ Object
This is optionally set to the array of objectClass names that are minimally required for EVERY object on your LDAP server. If you don’t want one, set this to [].
525 526 527 |
# File 'lib/activeldap/base.rb', line 525 def Base.required_classes [] end |
.schema ⇒ Object
Return the schema object
309 310 311 |
# File 'lib/activeldap/base.rb', line 309 def Base.schema @@schema end |
.search(config = {}) ⇒ Object
search
Wraps Ruby/LDAP connection.search to make it easier to search for specific data without cracking open Base.connection
317 318 319 320 321 322 323 324 325 326 327 328 329 330 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 |
# File 'lib/activeldap/base.rb', line 317 def Base.search(config={}) unless Base.connection if @@config ActiveLDAP::Base.connect(@@config) else ActiveLDAP::Base.connect end end 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| 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() # 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 |
Instance Method Details
#__methods ⇒ Object
Add available attributes to the methods
967 |
# File 'lib/activeldap/base.rb', line 967 alias_method :__methods, :methods |
#attributes ⇒ Object
attributes
Return attribute methods so that a program can determine available attributes dynamically without schema awareness
656 657 658 659 660 |
# File 'lib/activeldap/base.rb', line 656 def attributes @@logger.debug("stub: attributes called") send(:apply_objectclass, @data['objectClass']) if @data['objectClass'] != @last_oc return @attr_methods.keys end |
#delete ⇒ Object
delete
Delete this entry from LDAP
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 |
# File 'lib/activeldap/base.rb', line 725 def delete @@logger.debug("stub: delete called") 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. 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 |
#dn ⇒ Object
dn
Return the authoritative dn
673 674 675 676 |
# File 'lib/activeldap/base.rb', line 673 def dn @@logger.debug("stub: dn called") return @dn.dup end |
#exists? ⇒ Boolean
exists?
Return whether the entry exists in LDAP or not
665 666 667 668 |
# File 'lib/activeldap/base.rb', line 665 def exists? @@logger.debug("stub: exists? called") return @exists end |
#methods ⇒ Object
968 969 970 |
# File 'lib/activeldap/base.rb', line 968 def methods return __methods + attributes() end |
#validate ⇒ Object
validate
Basic validation:
-
Verify that every ‘MUST’ specified in the schema has a value defined
-
Enforcement of undefined attributes is handled in the objectClass= method
Must call enforce_types() first before enforcement can be guaranteed
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 |
# File 'lib/activeldap/base.rb', line 684 def validate @@logger.debug("stub: validate called") # Clean up attr values, etc send(:enforce_types) # Validate objectclass settings @data['objectClass'].each do |klass| unless klass.class == String raise TypeError, "Value in objectClass array is not a String. (#{klass.class}:#{klass.inspect})" end unless Base.schema.names("objectClasses").member? klass raise ObjectClassError, "objectClass '#{klass}' unknown to LDAP server." end end # make sure this doesn't drop any of the required objectclasses required_classes().each do |oc| unless @data['objectClass'].member? oc.to_s raise ObjectClassError, "'#{oc}' must be a defined objectClass for class '#{self.class}' as set in the ldap_mapping" end end # Make sure all MUST attributes have a value @data['objectClass'].each do |objc| @must.each do |req_attr| deref = @attr_methods[req_attr] # Set default if it wasn't yet set. @data[deref] = [] if @data[deref].nil? # Check for missing requirements. if @data[deref].empty? raise AttributeEmpty, "objectClass '#{objc}' requires attribute '#{Base.schema.attribute_aliases(req_attr).join(', ')}'" end end end @@logger.debug("stub: validate finished") end |
#write ⇒ Object
write
Write and validate this object into LDAP either adding or replacing attributes TODO: Binary data support TODO: Relative DN support
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 |
# File 'lib/activeldap/base.rb', line 751 def write @@logger.debug("stub: write called") # Validate against the objectClass requirements validate # Put all changes into one change entry to ensure # automatic rollback upon failure. entry = [] # 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 = 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 suffix, real_value = extract_subtypes(value) if ldap_data.has_key? key + suffix ldap_data[key + suffix].push(real_value) else ldap_data[key + suffix] = real_value end ldap_data[key].delete(value) end end 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 = 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 suffix, real_value = extract_subtypes(value) if data.has_key? key + suffix data[key + suffix].push(real_value) else data[key + suffix] = real_value end data[key].delete(value) end end end @@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") ldap_data.each do |pair| suffix = '' binary = 0 name, *suffix_a = pair[0].split(/;/) suffix = ';'+ suffix_a.join(';') if suffix_a.size > 0 name = @attr_methods[name] name = pair[0].split(/;/)[0] if name.nil? # for objectClass, or removed vals value = data[name+suffix] # If it doesn't exist, don't freak out. value = [] if value.nil? # Detect subtypes and account for them binary = LDAP::LDAP_MOD_BVALUES if Base.schema.binary? name replaceable.push(name+suffix) if pair[1] != value # Create mod entries if not value.empty? # Ditched delete then replace because attribs with no equality match rules # will fails @@logger.debug("updating attribute of existing entry: #{name+suffix}: #{value.inspect}") entry.push(LDAP.mod(LDAP::LDAP_MOD_REPLACE|binary, name + suffix, value)) else # Since some types do not have equality matching rules, delete doesn't work # Replacing with nothing is equivalent. @@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") data.each do |pair| suffix = '' binary = 0 name, *suffix_a = pair[0].split(/;/) suffix = ';' + suffix_a.join(';') if suffix_a.size > 0 name = @attr_methods[name] name = pair[0].split(/;/)[0] if name.nil? # for obj class or removed vals value = pair[1] # Make sure to change this to an Array if there was mistake earlier. value = [] if value.nil? if not replaceable.member? name+suffix # Detect subtypes and account for them binary = LDAP::LDAP_MOD_BVALUES if Base.schema.binary? name @@logger.debug("adding attribute to existing entry: #{name+suffix}: #{value.inspect}") # 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: 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}" end else # add everything! @@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', data[@attr_methods['objectClass']])) data.each do |pair| if pair[1].size > 0 and pair[0] != 'objectClass' and pair[0] != @attr_methods[dnattr()] # Detect subtypes and account for them if Base.schema.binary? pair[0].split(/;/)[0] binary = LDAP::LDAP_MOD_BVALUES else binary = 0 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 @@logger.debug("#write: adding #{@dn}") @@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") end |