Cleanup of Dataset and Entry.
This commit is contained in:
parent
20f494a875
commit
06ea324055
|
@ -1041,7 +1041,7 @@ class Net::LDAP
|
||||||
:attributes => [ :namingContexts, :supportedLdapVersion,
|
:attributes => [ :namingContexts, :supportedLdapVersion,
|
||||||
:altServer, :supportedControl, :supportedExtension,
|
:altServer, :supportedControl, :supportedExtension,
|
||||||
:supportedFeatures, :supportedSASLMechanisms])
|
:supportedFeatures, :supportedSASLMechanisms])
|
||||||
(rs and rs.first) or Entry.new
|
(rs and rs.first) or Net::LDAP::Entry.new
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return the root Subschema record from the LDAP server as a
|
# 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 => "",
|
rs = search(:ignore_server_caps => true, :base => "",
|
||||||
:scope => SearchScope_BaseObject,
|
:scope => SearchScope_BaseObject,
|
||||||
:attributes => [:subschemaSubentry])
|
: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
|
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,
|
rs = search(:ignore_server_caps => true, :base => subschema_name.first,
|
||||||
:scope => SearchScope_BaseObject,
|
:scope => SearchScope_BaseObject,
|
||||||
:filter => "objectclass=subschema",
|
:filter => "objectclass=subschema",
|
||||||
:attributes => [:objectclasses, :attributetypes])
|
:attributes => [:objectclasses, :attributetypes])
|
||||||
(rs and rs.first) or Entry.new
|
(rs and rs.first) or Net::LDAP::Entry.new
|
||||||
end
|
end
|
||||||
|
|
||||||
#--
|
#--
|
||||||
|
|
|
@ -20,80 +20,155 @@
|
||||||
#
|
#
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
module Net
|
##
|
||||||
class LDAP
|
# An LDAP Dataset. Used primarily as an intermediate format for converting
|
||||||
class Dataset < Hash
|
# to and from LDIF strings and Net::LDAP::Entry objects.
|
||||||
attr_reader :comments
|
class Net::LDAP::Dataset < Hash
|
||||||
|
##
|
||||||
|
# Dataset object comments.
|
||||||
|
attr_reader :comments
|
||||||
|
|
||||||
class IOFilter
|
class << self
|
||||||
def initialize(io)
|
class ChompedIO #:nodoc:
|
||||||
@io = io
|
def initialize(io)
|
||||||
end
|
@io = io
|
||||||
def gets
|
end
|
||||||
s = @io.gets
|
def gets
|
||||||
s.chomp if s
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.read_ldif io
|
ds
|
||||||
ds = Dataset.new
|
end
|
||||||
io = IOFilter.new(io)
|
|
||||||
|
|
||||||
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
|
def initialize(*args, &block) #:nodoc:
|
||||||
new_line = io.gets
|
super
|
||||||
if new_line =~ /^[\s]+/
|
@comments = []
|
||||||
line << " " << $'
|
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
|
else
|
||||||
nextline = new_line
|
ary << "#{attr}: #{value}"
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ds
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ary << ""
|
||||||
|
end
|
||||||
|
block_given? and ary.each { |line| yield line}
|
||||||
|
|
||||||
def initialize
|
ary
|
||||||
@comments = []
|
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
|
end
|
||||||
|
ary << entry
|
||||||
|
end
|
||||||
|
ary
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is an internal convenience method to determine if a value requires
|
||||||
def to_ldif
|
# base64-encoding before conversion to LDIF output. The standard approach
|
||||||
ary = []
|
# in most LDAP tools is to check whether the value is a password, or if
|
||||||
ary += (@comments || [])
|
# the first or last bytes are non-printable. Microsoft Active Directory,
|
||||||
keys.sort.each do |dn|
|
# on the other hand, sometimes sends values that are binary in the middle.
|
||||||
ary << "dn: #{dn}"
|
#
|
||||||
|
# In the worst cases, this could be a nasty performance killer, which is
|
||||||
self[dn].keys.map {|sym| sym.to_s}.sort.each do |attr|
|
# why we handle the simplest cases first. Ideally, we would also test the
|
||||||
self[dn][attr.intern].each {|val| ary << "#{attr}: #{val}" }
|
# first/last byte, but it's a bit harder to do this in a way that's
|
||||||
end
|
# compatible with both 1.8.6 and 1.8.7.
|
||||||
|
def value_is_binary?(value)
|
||||||
ary << ""
|
value = value.to_s
|
||||||
end
|
return true if value[0] == ?: or value[0] == ?<
|
||||||
block_given? and ary.each {|line| yield line}
|
value.each_byte { |byte| return true if (byte < 32) || (byte > 126) }
|
||||||
ary
|
false
|
||||||
end
|
end
|
||||||
|
private :value_is_binary?
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'net/ldap/entry' unless defined? Net::LDAP::Entry
|
||||||
|
|
|
@ -21,246 +21,188 @@
|
||||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
# 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 <i>values.</i> 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.
|
class << self
|
||||||
# User code generally does not instantiate this class. Net::LDAP#search
|
##
|
||||||
# provides objects of this class to user code, either as block parameters or
|
# Converts a single LDIF entry string into an Entry object. Useful for
|
||||||
# as return values.
|
# 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
|
# When an attribute is set using this, that attribute is now made
|
||||||
# and globally identified by a DN ("Distinguished Name"). Attributes are
|
# accessible through methods as well.
|
||||||
# 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
|
# entry = Net::LDAP::Entry.new("dc=com")
|
||||||
# range of characters allowed in attribute names. To simplify handling
|
# entry.foo # => NoMethodError
|
||||||
# attribute names, Net::LDAP::Entry internally converts them to a standard
|
# entry["foo"] = 12345 # => [12345]
|
||||||
# format. Therefore, the methods which take attribute names can take Strings
|
# entry.foo # => [12345]
|
||||||
# or Symbols, and work correctly regardless of case or capitalization.
|
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 <i>values.</i> An
|
# Calls a user-supplied block with each attribute in turn, passing two
|
||||||
# entry is the combination of a unique DN, a set of attribute names, and a
|
# arguments to the block: a Symbol giving the name of the attribute, and a
|
||||||
# (possibly-empty) array of values for each attribute.
|
# (possibly empty) \Array of data values.
|
||||||
#
|
def each # :yields: attribute-name, data-values-array
|
||||||
# Class Net::LDAP::Entry provides convenience methods for dealing with LDAP
|
if block_given?
|
||||||
# entries. In addition to the methods documented below, you may access
|
attribute_names.each {|a|
|
||||||
# individual attributes of an entry simply by giving the attribute name as
|
attr_name,values = a,self[a]
|
||||||
# the name of a method call. For example:
|
yield attr_name, values
|
||||||
#
|
|
||||||
# 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)
|
|
||||||
}
|
}
|
||||||
if v[0..0] == ':' or v[0..0] == '<'
|
end
|
||||||
return true
|
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
|
end
|
||||||
false
|
|
||||||
end
|
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
|
# Given a valid attribute symbol, returns true.
|
||||||
end # module Net
|
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
|
||||||
|
|
|
@ -7,53 +7,60 @@ require 'digest/sha1'
|
||||||
require 'base64'
|
require 'base64'
|
||||||
|
|
||||||
class TestLdif < Test::Unit::TestCase
|
class TestLdif < Test::Unit::TestCase
|
||||||
|
|
||||||
TestLdifFilename = "#{File.dirname(__FILE__)}/testdata.ldif"
|
TestLdifFilename = "#{File.dirname(__FILE__)}/testdata.ldif"
|
||||||
|
|
||||||
def test_empty_ldif
|
def test_empty_ldif
|
||||||
ds = Net::LDAP::Dataset.read_ldif( StringIO.new )
|
ds = Net::LDAP::Dataset.read_ldif(StringIO.new)
|
||||||
assert_equal( true, ds.empty? )
|
assert_equal(true, ds.empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_ldif_with_comments
|
def test_ldif_with_comments
|
||||||
str = ["# Hello from LDIF-land", "# This is an unterminated comment"]
|
str = ["# Hello from LDIF-land", "# This is an unterminated comment"]
|
||||||
io = StringIO.new( str[0] + "\r\n" + str[1] )
|
io = StringIO.new(str[0] + "\r\n" + str[1])
|
||||||
ds = Net::LDAP::Dataset::read_ldif( io )
|
ds = Net::LDAP::Dataset::read_ldif(io)
|
||||||
assert_equal( str, ds.comments )
|
assert_equal(str, ds.comments)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_ldif_with_password
|
def test_ldif_with_password
|
||||||
psw = "goldbricks"
|
psw = "goldbricks"
|
||||||
hashed_psw = "{SHA}" + Base64::encode64(Digest::SHA1.digest(psw)).chomp
|
hashed_psw = "{SHA}" + Base64::encode64(Digest::SHA1.digest(psw)).chomp
|
||||||
|
|
||||||
ldif_encoded = Base64::encode64( hashed_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" ))
|
ds = Net::LDAP::Dataset::read_ldif(StringIO.new("dn: Goldbrick\r\nuserPassword:: #{ldif_encoded}\r\n\r\n"))
|
||||||
recovered_psw = ds["Goldbrick"][:userpassword].shift
|
recovered_psw = ds["Goldbrick"][:userpassword].shift
|
||||||
assert_equal( hashed_psw, recovered_psw )
|
assert_equal(hashed_psw, recovered_psw)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_ldif_with_continuation_lines
|
def test_ldif_with_continuation_lines
|
||||||
ds = Net::LDAP::Dataset::read_ldif( StringIO.new( "dn: abcdefg\r\n hijklmn\r\n\r\n" ))
|
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" ))
|
assert_equal(true, ds.has_key?("abcdefg hijklmn"))
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO, INADEQUATE. We need some more tests
|
# TODO, INADEQUATE. We need some more tests
|
||||||
# to verify the content.
|
# to verify the content.
|
||||||
def test_ldif
|
def test_ldif
|
||||||
File.open( TestLdifFilename, "r" ) {|f|
|
File.open(TestLdifFilename, "r") {|f|
|
||||||
ds = Net::LDAP::Dataset::read_ldif( f )
|
ds = Net::LDAP::Dataset::read_ldif(f)
|
||||||
assert_equal( 13, ds.length )
|
assert_equal(13, ds.length)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO, need some tests.
|
|
||||||
# Must test folded lines and base64-encoded lines as well as normal ones.
|
# Must test folded lines and base64-encoded lines as well as normal ones.
|
||||||
#def test_to_ldif
|
def test_to_ldif
|
||||||
# File.open( TestLdifFilename, "r" ) {|f|
|
data = File.open(TestLdifFilename, "rb") { |f| f.read }
|
||||||
# ds = Net::LDAP::Dataset::read_ldif( f )
|
io = StringIO.new(data)
|
||||||
# ds.to_ldif
|
|
||||||
# assert_equal( true, false ) # REMOVE WHEN WE HAVE SOME TESTS HERE.
|
|
||||||
# }
|
|
||||||
#end
|
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
Loading…
Reference in a new issue