diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb
index c0470a7..c616554 100644
--- a/lib/net/ldap.rb
+++ b/lib/net/ldap.rb
@@ -629,7 +629,7 @@ class Net::LDAP
if @open_connection
@result = @open_connection.search(args) { |entry|
result_set << entry if result_set
- yield(entry) if block_given?
+ yield entry if block_given?
}
else
@result = 0
@@ -639,7 +639,7 @@ class Net::LDAP
if (@result = conn.bind(args[:auth] || @auth)) == 0
@result = conn.search(args) { |entry|
result_set << entry if result_set
- yield(entry) if block_given?
+ yield entry if block_given?
}
end
ensure
@@ -1041,7 +1041,7 @@ class Net::LDAP
:attributes => [ :namingContexts, :supportedLdapVersion,
:altServer, :supportedControl, :supportedExtension,
:supportedFeatures, :supportedSASLMechanisms])
- (rs and rs.first) or Entry.new
+ (rs and rs.first) or Net::LDAP::Entry.new
end
# Return the root Subschema record from the LDAP server as a
@@ -1072,16 +1072,16 @@ class Net::LDAP
rs = search(:ignore_server_caps => true, :base => "",
:scope => SearchScope_BaseObject,
:attributes => [:subschemaSubentry])
- return Entry.new unless (rs and rs.first)
+ return Net::LDAP::Entry.new unless (rs and rs.first)
subschema_name = rs.first.subschemasubentry
- return Entry.new unless (subschema_name and subschema_name.first)
+ return Net::LDAP::Entry.new unless (subschema_name and subschema_name.first)
rs = search(:ignore_server_caps => true, :base => subschema_name.first,
:scope => SearchScope_BaseObject,
:filter => "objectclass=subschema",
:attributes => [:objectclasses, :attributetypes])
- (rs and rs.first) or Entry.new
+ (rs and rs.first) or Net::LDAP::Entry.new
end
#--
@@ -1405,7 +1405,7 @@ class Net::LDAP::Connection #:nodoc:
case pdu.app_tag
when 4 # search-data
n_results += 1
- yield(pdu.search_entry) if block_given?
+ yield pdu.search_entry if block_given?
when 19 # search-referral
if return_referrals
if block_given?
diff --git a/lib/net/ldap/dataset.rb b/lib/net/ldap/dataset.rb
index 19c1490..342c2e4 100644
--- a/lib/net/ldap/dataset.rb
+++ b/lib/net/ldap/dataset.rb
@@ -20,80 +20,155 @@
#
#---------------------------------------------------------------------------
-module Net
- class LDAP
- class Dataset < Hash
- attr_reader :comments
+##
+# An LDAP Dataset. Used primarily as an intermediate format for converting
+# to and from LDIF strings and Net::LDAP::Entry objects.
+class Net::LDAP::Dataset < Hash
+ ##
+ # Dataset object comments.
+ attr_reader :comments
- class IOFilter
- def initialize(io)
- @io = io
- end
- def gets
- s = @io.gets
- s.chomp if s
+ class << self
+ class ChompedIO #:nodoc:
+ def initialize(io)
+ @io = io
+ end
+ def gets
+ s = @io.gets
+ s.chomp if s
+ end
+ end
+
+ ##
+ # Reads an object that returns data line-wise (using #gets) and parses
+ # LDIF data into a Dataset object.
+ def read_ldif(io) #:yields: entry-type, value Used mostly for debugging.
+ ds = Net::LDAP::Dataset.new
+ io = ChompedIO.new(io)
+
+ line = io.gets
+ dn = nil
+
+ while line
+ new_line = io.gets
+
+ if new_line =~ /^[\s]+/
+ line << " " << $'
+ else
+ nextline = new_line
+
+ if line =~ /^#/
+ ds.comments << line
+ yield :comment, line if block_given?
+ elsif line =~ /^dn:[\s]*/i
+ dn = $'
+ ds[dn] = Hash.new { |k,v| k[v] = [] }
+ yield :dn, dn if block_given?
+ elsif line.empty?
+ dn = nil
+ yield :end, nil if block_given?
+ elsif line =~ /^([^:]+):([\:]?)[\s]*/
+ # $1 is the attribute name
+ # $2 is a colon iff the attr-value is base-64 encoded
+ # $' is the attr-value
+ # Avoid the Base64 class because not all Ruby versions have it.
+ attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
+ ds[dn][$1.downcase.to_sym] << attrvalue
+ yield :attr, [$1.downcase.to_sym, attrvalue] if block_given?
+ end
+
+ line = nextline
end
end
- def self.read_ldif io
- ds = Dataset.new
- io = IOFilter.new(io)
+ ds
+ end
- line = io.gets
- dn = nil
+ ##
+ # Creates a Dataset object from an Entry object. Used mostly to assist
+ # with the conversion of
+ def from_entry(entry)
+ dataset = Net::LDAP::Dataset.new
+ hash = { }
+ entry.each_attribute do |attribute, value|
+ next if attribute == :dn
+ hash[attribute] = value
+ end
+ dataset[entry.dn] = hash
+ dataset
+ end
+ end
- while line
- new_line = io.gets
- if new_line =~ /^[\s]+/
- line << " " << $'
+ def initialize(*args, &block) #:nodoc:
+ super
+ @comments = []
+ end
+
+ ##
+ # Outputs an LDAP Dataset as an array of strings representing LDIF
+ # entries.
+ def to_ldif
+ ary = []
+ ary += @comments unless @comments.empty?
+ keys.sort.each do |dn|
+ ary << "dn: #{dn}"
+
+ attributes = self[dn].keys.map { |attr| attr.to_s }.sort
+ attributes.each do |attr|
+ self[dn][attr.to_sym].each do |value|
+ if attr == "userpassword" or value_is_binary?(value)
+ value = [value].pack("m").chomp.gsub(/\n/m, "\n ")
+ ary << "#{attr}:: #{value}"
else
- nextline = new_line
-
- if line =~ /^\#/
- ds.comments << line
- elsif line =~ /^dn:[\s]*/i
- dn = $'
- ds[dn] = Hash.new {|k,v| k[v] = []}
- elsif line.length == 0
- dn = nil
- elsif line =~ /^([^:]+):([\:]?)[\s]*/
- # $1 is the attribute name
- # $2 is a colon iff the attr-value is base-64 encoded
- # $' is the attr-value
- # Avoid the Base64 class because not all Ruby versions have it.
- attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
- ds[dn][$1.downcase.intern] << attrvalue
- end
-
- line = nextline
+ ary << "#{attr}: #{value}"
end
end
-
- ds
end
+ ary << ""
+ end
+ block_given? and ary.each { |line| yield line}
- def initialize
- @comments = []
+ ary
+ end
+
+ ##
+ # Outputs an LDAP Dataset as an LDIF string.
+ def to_ldif_string
+ to_ldif.join("\n")
+ end
+
+ ##
+ # Convert the parsed LDIF objects to Net::LDAP::Entry objects.
+ def to_entries
+ ary = []
+ keys.each do |dn|
+ entry = Net::LDAP::Entry.new(dn)
+ self[dn].each do |attr, value|
+ entry[attr] = value
end
+ ary << entry
+ end
+ ary
+ end
-
- def to_ldif
- ary = []
- ary += (@comments || [])
- keys.sort.each do |dn|
- ary << "dn: #{dn}"
-
- self[dn].keys.map {|sym| sym.to_s}.sort.each do |attr|
- self[dn][attr.intern].each {|val| ary << "#{attr}: #{val}" }
- end
-
- ary << ""
- end
- block_given? and ary.each {|line| yield line}
- ary
- end
-
- end
- end
+ # This is an internal convenience method to determine if a value requires
+ # base64-encoding before conversion to LDIF output. The standard approach
+ # in most LDAP tools is to check whether the value is a password, or if
+ # the first or last bytes are non-printable. Microsoft Active Directory,
+ # on the other hand, sometimes sends values that are binary in the middle.
+ #
+ # In the worst cases, this could be a nasty performance killer, which is
+ # why we handle the simplest cases first. Ideally, we would also test the
+ # first/last byte, but it's a bit harder to do this in a way that's
+ # compatible with both 1.8.6 and 1.8.7.
+ def value_is_binary?(value)
+ value = value.to_s
+ return true if value[0] == ?: or value[0] == ?<
+ value.each_byte { |byte| return true if (byte < 32) || (byte > 126) }
+ false
+ end
+ private :value_is_binary?
end
+
+require 'net/ldap/entry' unless defined? Net::LDAP::Entry
diff --git a/lib/net/ldap/entry.rb b/lib/net/ldap/entry.rb
index 11daf2f..539ba0f 100644
--- a/lib/net/ldap/entry.rb
+++ b/lib/net/ldap/entry.rb
@@ -21,246 +21,188 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
+
+##
+# Objects of this class represent individual entries in an LDAP directory.
+# User code generally does not instantiate this class. Net::LDAP#search
+# provides objects of this class to user code, either as block parameters or
+# as return values.
#
+# In LDAP-land, an "entry" is a collection of attributes that are uniquely
+# and globally identified by a DN ("Distinguished Name"). Attributes are
+# identified by short, descriptive words or phrases. Although a directory is
+# free to implement any attribute name, most of them follow rigorous
+# standards so that the range of commonly-encountered attribute names is not
+# large.
+#
+# An attribute name is case-insensitive. Most directories also restrict the
+# range of characters allowed in attribute names. To simplify handling
+# attribute names, Net::LDAP::Entry internally converts them to a standard
+# format. Therefore, the methods which take attribute names can take Strings
+# or Symbols, and work correctly regardless of case or capitalization.
+#
+# An attribute consists of zero or more data items called values. An
+# entry is the combination of a unique DN, a set of attribute names, and a
+# (possibly-empty) array of values for each attribute.
+#
+# Class Net::LDAP::Entry provides convenience methods for dealing with LDAP
+# entries. In addition to the methods documented below, you may access
+# individual attributes of an entry simply by giving the attribute name as
+# the name of a method call. For example:
+#
+# ldap.search( ... ) do |entry|
+# puts "Common name: #{entry.cn}"
+# puts "Email addresses:"
+# entry.mail.each {|ma| puts ma}
+# end
+#
+# If you use this technique to access an attribute that is not present in a
+# particular Entry object, a NoMethodError exception will be raised.
+#
+#--
+# Ugly problem to fix someday: We key off the internal hash with a canonical
+# form of the attribute name: convert to a string, downcase, then take the
+# symbol. Unfortunately we do this in at least three places. Should do it in
+# ONE place.
+class Net::LDAP::Entry
+ ##
+ # This constructor is not generally called by user code.
+ def initialize(dn = nil) #:nodoc:
+ @myhash = {}
+ @myhash[:dn] = [dn]
+ end
-module Net
-class LDAP
+ ##
+ # Use the LDIF format for Marshal serialization.
+ def _dump(depth) #:nodoc:
+ to_ldif
+ end
+ ##
+ # Use the LDIF format for Marshal serialization.
+ def self._load(entry) #:nodoc:
+ from_single_ldif_string(entry)
+ end
- # Objects of this class represent individual entries in an LDAP directory.
- # User code generally does not instantiate this class. Net::LDAP#search
- # provides objects of this class to user code, either as block parameters or
- # as return values.
+ class << self
+ ##
+ # Converts a single LDIF entry string into an Entry object. Useful for
+ # Marshal serialization. If a string with multiple LDIF entries is
+ # provided, an exception will be raised.
+ def from_single_ldif_string(ldif)
+ ds = Net::LDAP::Dataset.read_ldif(::StringIO.new(ldif))
+
+ return nil if ds.empty?
+
+ raise Net::LDAP::LdapError, "Too many LDIF entries" unless ds.size == 1
+
+ entry = ds.to_entries.first
+
+ return nil if entry.dn.nil?
+ entry
+ end
+
+ ##
+ # Canonicalizes an LDAP attribute name as a \Symbol. The name is
+ # lowercased and, if present, a trailing equals sign is removed.
+ def attribute_name(name)
+ name = name.to_s.downcase
+ name = name[0..-2] if name[-1] == ?=
+ name.to_sym
+ end
+ end
+
+ ##
+ # Sets or replaces the array of values for the provided attribute. The
+ # attribute name is canonicalized prior to assignment.
#
- # In LDAP-land, an "entry" is a collection of attributes that are uniquely
- # and globally identified by a DN ("Distinguished Name"). Attributes are
- # identified by short, descriptive words or phrases. Although a directory is
- # free to implement any attribute name, most of them follow rigorous
- # standards so that the range of commonly-encountered attribute names is not
- # large.
+ # When an attribute is set using this, that attribute is now made
+ # accessible through methods as well.
#
- # An attribute name is case-insensitive. Most directories also restrict the
- # range of characters allowed in attribute names. To simplify handling
- # attribute names, Net::LDAP::Entry internally converts them to a standard
- # format. Therefore, the methods which take attribute names can take Strings
- # or Symbols, and work correctly regardless of case or capitalization.
+ # entry = Net::LDAP::Entry.new("dc=com")
+ # entry.foo # => NoMethodError
+ # entry["foo"] = 12345 # => [12345]
+ # entry.foo # => [12345]
+ def []=(name, value)
+ @myhash[self.class.attribute_name(name)] = Kernel::Array(value)
+ end
+
+ ##
+ # Reads the array of values for the provided attribute. The attribute name
+ # is canonicalized prior to reading. Returns an empty array if the
+ # attribute does not exist.
+ def [](name)
+ name = self.class.attribute_name(name)
+ @myhash[name] || []
+ end
+
+ ##
+ # Returns the first distinguished name (dn) of the Entry as a \String.
+ def dn
+ self[:dn].first.to_s
+ end
+
+ ##
+ # Returns an array of the attribute names present in the Entry.
+ def attribute_names
+ @myhash.keys
+ end
+
+ ##
+ # Accesses each of the attributes present in the Entry.
#
- # An attribute consists of zero or more data items called values. An
- # entry is the combination of a unique DN, a set of attribute names, and a
- # (possibly-empty) array of values for each attribute.
- #
- # Class Net::LDAP::Entry provides convenience methods for dealing with LDAP
- # entries. In addition to the methods documented below, you may access
- # individual attributes of an entry simply by giving the attribute name as
- # the name of a method call. For example:
- #
- # ldap.search( ... ) do |entry|
- # puts "Common name: #{entry.cn}"
- # puts "Email addresses:"
- # entry.mail.each {|ma| puts ma}
- # end
- #
- # If you use this technique to access an attribute that is not present in a
- # particular Entry object, a NoMethodError exception will be raised.
- #
- #--
- # Ugly problem to fix someday: We key off the internal hash with a canonical
- # form of the attribute name: convert to a string, downcase, then take the
- # symbol. Unfortunately we do this in at least three places. Should do it in
- # ONE place.
- #
- class Entry
- # This constructor is not generally called by user code.
- #
- def initialize dn = nil # :nodoc:
- @myhash = {}
- @myhash[:dn] = [dn]
- end
-
- def _dump depth
- to_ldif
- end
-
- class << self
- def _load entry
- from_single_ldif_string entry
- end
- end
-
- #--
- # Discovered bug, 26Aug06: I noticed that we're not converting the
- # incoming value to an array if it isn't already one.
- def []=(name, value) # :nodoc:
- sym = attribute_name(name)
- value = [value] unless value.is_a?(Array)
- @myhash[sym] = value
- end
-
- #--
- # We have to deal with this one as we do with []= because this one and not
- # the other one gets called in formulations like entry["CN"] << cn.
- #
- def [](name) # :nodoc:
- name = attribute_name(name) unless name.is_a?(Symbol)
- @myhash[name] || []
- end
-
- # Returns the dn of the Entry as a String.
- def dn
- self[:dn][0].to_s
- end
-
- # Returns an array of the attribute names present in the Entry.
- def attribute_names
- @myhash.keys
- end
-
- # Accesses each of the attributes present in the Entry.
- # Calls a user-supplied block with each attribute in turn,
- # passing two arguments to the block: a Symbol giving
- # the name of the attribute, and a (possibly empty)
- # Array of data values.
- #
- def each
- if block_given?
- attribute_names.each {|a|
- attr_name,values = a,self[a]
- yield attr_name, values
- }
- end
- end
-
- alias_method :each_attribute, :each
-
- # Converts the Entry to a String, representing the
- # Entry's attributes in LDIF format.
- #--
- def to_ldif
- ary = []
- ary << "dn: #{dn}\n"
- v2 = "" # temp value, save on GC
- each_attribute do |k,v|
- unless k == :dn
- v.each {|v1|
- v2 = if (k == :userpassword) || is_attribute_value_binary?(v1)
- ": #{Base64.encode64(v1).chomp.gsub(/\n/m,"\n ")}"
- else
- " #{v1}"
- end
- ary << "#{k}:#{v2}\n"
- }
- end
- end
- ary << "\n"
- ary.join
- end
-
- #--
- # TODO, doesn't support broken lines.
- # It generates a SINGLE Entry object from an incoming LDIF stream which is
- # of course useless for big LDIF streams that encode many objects.
- #
- # DO NOT DOCUMENT THIS METHOD UNTIL THESE RESTRICTIONS ARE LIFTED.
- #
- # As it is, it's useful for unmarshalling objects that we create, but not
- # for reading arbitrary LDIF files. Eventually, we should have a class
- # method that parses large LDIF streams into individual LDIF blocks
- # (delimited by blank lines) and passes them here.
- #
- class << self
- def from_single_ldif_string ldif
- entry = Entry.new
- entry[:dn] = []
- ldif.split(/\r?\n/m).each {|line|
- break if line.length == 0
- if line =~ /\A([\w]+):(:?)[\s]*/
- entry[$1] <<= if $2 == ':'
- Base64.decode64($')
- else
- $'
- end
- end
- }
- entry.dn ? entry : nil
- end
- end
-
- #--
- # Part of the support for getter and setter style access to attributes.
- #
- def respond_to?(sym)
- name = attribute_name(sym)
- return true if valid_attribute?(name)
- return super
- end
-
- #--
- # Supports getter and setter style access for all the attributes that this
- # entry holds.
- #
- def method_missing sym, *args, &block # :nodoc:
- name = attribute_name(sym)
-
- if valid_attribute? name
- if setter?(sym) && args.size == 1
- value = args.first
- value = [value] unless value.instance_of?(Array)
- self[name]= value
-
- return value
- elsif args.empty?
- return self[name]
- end
- end
-
- super
- end
-
- def write
- end
-
- private
-
- #--
- # Internal convenience method. It seems like the standard
- # approach in most LDAP tools to base64 encode an attribute
- # value if its first or last byte is nonprintable, or if
- # it's a password. But that turns out to be not nearly good
- # enough. There are plenty of A/D attributes that are binary
- # in the middle. This is probably a nasty performance killer.
- def is_attribute_value_binary? value
- v = value.to_s
- v.each_byte {|byt|
- return true if (byt < 32) || (byt > 126)
+ # Calls a user-supplied block with each attribute in turn, passing two
+ # arguments to the block: a Symbol giving the name of the attribute, and a
+ # (possibly empty) \Array of data values.
+ def each # :yields: attribute-name, data-values-array
+ if block_given?
+ attribute_names.each {|a|
+ attr_name,values = a,self[a]
+ yield attr_name, values
}
- if v[0..0] == ':' or v[0..0] == '<'
- return true
+ end
+ end
+ alias_method :each_attribute, :each
+
+ ##
+ # Converts the Entry to an LDIF-formatted String
+ def to_ldif
+ Net::LDAP::Dataset.from_entry(self).to_ldif_string
+ end
+
+ def respond_to?(sym) #:nodoc:
+ return true if valid_attribute?(self.class.attribute_name(sym))
+ return super
+ end
+
+ def method_missing(sym, *args, &block) #:nodoc:
+ name = self.class.attribute_name(sym)
+
+ if valid_attribute?(name )
+ if setter?(sym) && args.size == 1
+ value = args.first
+ value = Array(value)
+ self[name]= value
+ return value
+ elsif args.empty?
+ return self[name]
end
- false
end
-
- # Returns the symbol that can be used to access the attribute that
- # sym_or_str designates.
- #
- def attribute_name(sym_or_str)
- str = sym_or_str.to_s.downcase
-
- # Does str match 'something='? Still only returns :something
- return str[0...-1].to_sym if str.size>1 && str[-1] == ?=
- return str.to_sym
- end
-
- # Given a valid attribute symbol, returns true.
- #
- def valid_attribute?(attr_name)
- attribute_names.include?(attr_name)
- end
-
- def setter?(sym)
- sym.to_s[-1] == ?=
- end
- end # class Entry
+ super
+ end
-end # class LDAP
-end # module Net
+ # Given a valid attribute symbol, returns true.
+ def valid_attribute?(attr_name)
+ attribute_names.include?(attr_name)
+ end
+ private :valid_attribute?
+
+ # Returns true if the symbol ends with an equal sign.
+ def setter?(sym)
+ sym.to_s[-1] == ?=
+ end
+ private :setter?
+end # class Entry
+
+require 'net/ldap/dataset' unless defined? Net::LDAP::Dataset
diff --git a/test/test_ldif.rb b/test/test_ldif.rb
index 2f7340f..b2c9f8c 100644
--- a/test/test_ldif.rb
+++ b/test/test_ldif.rb
@@ -7,53 +7,60 @@ require 'digest/sha1'
require 'base64'
class TestLdif < Test::Unit::TestCase
-
TestLdifFilename = "#{File.dirname(__FILE__)}/testdata.ldif"
def test_empty_ldif
- ds = Net::LDAP::Dataset.read_ldif( StringIO.new )
- assert_equal( true, ds.empty? )
+ ds = Net::LDAP::Dataset.read_ldif(StringIO.new)
+ assert_equal(true, ds.empty?)
end
def test_ldif_with_comments
str = ["# Hello from LDIF-land", "# This is an unterminated comment"]
- io = StringIO.new( str[0] + "\r\n" + str[1] )
- ds = Net::LDAP::Dataset::read_ldif( io )
- assert_equal( str, ds.comments )
+ io = StringIO.new(str[0] + "\r\n" + str[1])
+ ds = Net::LDAP::Dataset::read_ldif(io)
+ assert_equal(str, ds.comments)
end
def test_ldif_with_password
psw = "goldbricks"
hashed_psw = "{SHA}" + Base64::encode64(Digest::SHA1.digest(psw)).chomp
- ldif_encoded = Base64::encode64( hashed_psw ).chomp
- ds = Net::LDAP::Dataset::read_ldif( StringIO.new( "dn: Goldbrick\r\nuserPassword:: #{ldif_encoded}\r\n\r\n" ))
+ ldif_encoded = Base64::encode64(hashed_psw).chomp
+ ds = Net::LDAP::Dataset::read_ldif(StringIO.new("dn: Goldbrick\r\nuserPassword:: #{ldif_encoded}\r\n\r\n"))
recovered_psw = ds["Goldbrick"][:userpassword].shift
- assert_equal( hashed_psw, recovered_psw )
+ assert_equal(hashed_psw, recovered_psw)
end
def test_ldif_with_continuation_lines
- ds = Net::LDAP::Dataset::read_ldif( StringIO.new( "dn: abcdefg\r\n hijklmn\r\n\r\n" ))
- assert_equal( true, ds.has_key?( "abcdefg hijklmn" ))
+ ds = Net::LDAP::Dataset::read_ldif(StringIO.new("dn: abcdefg\r\n hijklmn\r\n\r\n"))
+ assert_equal(true, ds.has_key?("abcdefg hijklmn"))
end
# TODO, INADEQUATE. We need some more tests
# to verify the content.
def test_ldif
- File.open( TestLdifFilename, "r" ) {|f|
- ds = Net::LDAP::Dataset::read_ldif( f )
- assert_equal( 13, ds.length )
+ File.open(TestLdifFilename, "r") {|f|
+ ds = Net::LDAP::Dataset::read_ldif(f)
+ assert_equal(13, ds.length)
}
end
- # TODO, need some tests.
# Must test folded lines and base64-encoded lines as well as normal ones.
- #def test_to_ldif
- # File.open( TestLdifFilename, "r" ) {|f|
- # ds = Net::LDAP::Dataset::read_ldif( f )
- # ds.to_ldif
- # assert_equal( true, false ) # REMOVE WHEN WE HAVE SOME TESTS HERE.
- # }
- #end
+ def test_to_ldif
+ data = File.open(TestLdifFilename, "rb") { |f| f.read }
+ io = StringIO.new(data)
+ entries = data.grep(/^dn:\s*/) { $'.chomp }
+ dn_entries = entries.dup
+
+ ds = Net::LDAP::Dataset::read_ldif(io) { |type, value|
+ case type
+ when :dn
+ assert_equal(dn_entries.first, value)
+ dn_entries.shift
+ end
+ }
+ assert_equal(entries.size, ds.size)
+ assert_equal(entries.sort, ds.to_ldif.grep(/^dn:\s*/) { $'.chomp })
+ end
end