lib/net/ldap/entry.rb in net-ldap-0.1.1 vs lib/net/ldap/entry.rb in net-ldap-0.2
- old
+ new
@@ -1,266 +1,185 @@
-# LDAP Entry (search-result) support classes
+# -*- ruby encoding: utf-8 -*-
+##
+# 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.
#
-# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
+# 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.
#
-# Gmail: garbagecat10
+# 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.
#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# 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:
#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+# ldap.search( ... ) do |entry|
+# puts "Common name: #{entry.cn}"
+# puts "Email addresses:"
+# entry.mail.each {|ma| puts ma}
+# end
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+# 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.
- #
- # 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 Entry
- # This constructor is not generally called by user code.
- #
- def initialize dn = nil # :nodoc:
- @myhash = {}
- @myhash[:dn] = [dn]
- end
+ 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))
- def _dump depth
- to_ldif
- end
+ return nil if ds.empty?
- class << self
- def _load entry
- from_single_ldif_string entry
- end
- end
+ raise Net::LDAP::LdapError, "Too many LDIF entries" unless ds.size == 1
- #--
- # 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
+ entry = ds.to_entries.first
- #--
- # 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] || []
+ return nil if entry.dn.nil?
+ entry
end
- # Returns the dn of the Entry as a String.
- def dn
- self[:dn][0].to_s
+ ##
+ # 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
- # Returns an array of the attribute names present in the Entry.
- def attribute_names
- @myhash.keys
- end
+ ##
+ # Sets or replaces the array of values for the provided attribute. The
+ # attribute name is canonicalized prior to assignment.
+ #
+ # When an attribute is set using this, that attribute is now made
+ # accessible through methods as well.
+ #
+ # 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
- # 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
+ ##
+ # 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
- alias_method :each_attribute, :each
+ ##
+ # Returns the first distinguished name (dn) of the Entry as a \String.
+ def dn
+ self[:dn].first.to_s
+ end
- # 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
+ ##
+ # Returns an array of the attribute names present in the Entry.
+ def attribute_names
+ @myhash.keys
+ 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
+ ##
+ # 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 # :yields: attribute-name, data-values-array
+ if block_given?
+ attribute_names.each {|a|
+ attr_name,values = a,self[a]
+ yield attr_name, values
+ }
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
+ end
+ alias_method :each_attribute, :each
- #--
- # 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
+ ##
+ # Converts the Entry to an LDIF-formatted String
+ def to_ldif
+ Net::LDAP::Dataset.from_entry(self).to_ldif_string
+ end
- return value
- elsif args.empty?
- return self[name]
- end
- end
-
- super
- end
+ def respond_to?(sym) #:nodoc:
+ return true if valid_attribute?(self.class.attribute_name(sym))
+ return super
+ end
- def write
- end
+ def method_missing(sym, *args, &block) #:nodoc:
+ name = self.class.attribute_name(sym)
- 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] == '<'
- return true
+ 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