prathe-net-ldap 0.2.20110317223538

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. data/.autotest +11 -0
  2. data/.gemtest +0 -0
  3. data/.rspec +2 -0
  4. data/Contributors.rdoc +21 -0
  5. data/Hacking.rdoc +68 -0
  6. data/History.rdoc +172 -0
  7. data/License.rdoc +29 -0
  8. data/Manifest.txt +49 -0
  9. data/README.rdoc +52 -0
  10. data/Rakefile +75 -0
  11. data/autotest/discover.rb +1 -0
  12. data/lib/net-ldap.rb +2 -0
  13. data/lib/net/ber.rb +318 -0
  14. data/lib/net/ber/ber_parser.rb +168 -0
  15. data/lib/net/ber/core_ext.rb +62 -0
  16. data/lib/net/ber/core_ext/array.rb +82 -0
  17. data/lib/net/ber/core_ext/bignum.rb +22 -0
  18. data/lib/net/ber/core_ext/false_class.rb +10 -0
  19. data/lib/net/ber/core_ext/fixnum.rb +66 -0
  20. data/lib/net/ber/core_ext/string.rb +60 -0
  21. data/lib/net/ber/core_ext/true_class.rb +12 -0
  22. data/lib/net/ldap.rb +1556 -0
  23. data/lib/net/ldap/dataset.rb +154 -0
  24. data/lib/net/ldap/dn.rb +225 -0
  25. data/lib/net/ldap/entry.rb +185 -0
  26. data/lib/net/ldap/filter.rb +759 -0
  27. data/lib/net/ldap/password.rb +31 -0
  28. data/lib/net/ldap/pdu.rb +256 -0
  29. data/lib/net/snmp.rb +268 -0
  30. data/net-ldap.gemspec +59 -0
  31. data/spec/integration/ssl_ber_spec.rb +36 -0
  32. data/spec/spec.opts +2 -0
  33. data/spec/spec_helper.rb +5 -0
  34. data/spec/unit/ber/ber_spec.rb +109 -0
  35. data/spec/unit/ber/core_ext/string_spec.rb +51 -0
  36. data/spec/unit/ldap/dn_spec.rb +80 -0
  37. data/spec/unit/ldap/entry_spec.rb +51 -0
  38. data/spec/unit/ldap/filter_spec.rb +84 -0
  39. data/spec/unit/ldap_spec.rb +48 -0
  40. data/test/common.rb +3 -0
  41. data/test/test_entry.rb +59 -0
  42. data/test/test_filter.rb +122 -0
  43. data/test/test_ldap_connection.rb +24 -0
  44. data/test/test_ldif.rb +79 -0
  45. data/test/test_password.rb +17 -0
  46. data/test/test_rename.rb +77 -0
  47. data/test/test_snmp.rb +114 -0
  48. data/test/testdata.ldif +101 -0
  49. data/testserver/ldapserver.rb +210 -0
  50. data/testserver/testdata.ldif +101 -0
  51. metadata +206 -0
@@ -0,0 +1,154 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+ ##
3
+ # An LDAP Dataset. Used primarily as an intermediate format for converting
4
+ # to and from LDIF strings and Net::LDAP::Entry objects.
5
+ class Net::LDAP::Dataset < Hash
6
+ ##
7
+ # Dataset object comments.
8
+ attr_reader :comments
9
+
10
+ def initialize(*args, &block) # :nodoc:
11
+ super
12
+ @comments = []
13
+ end
14
+
15
+ ##
16
+ # Outputs an LDAP Dataset as an array of strings representing LDIF
17
+ # entries.
18
+ def to_ldif
19
+ ary = []
20
+ ary += @comments unless @comments.empty?
21
+ keys.sort.each do |dn|
22
+ ary << "dn: #{dn}"
23
+
24
+ attributes = self[dn].keys.map { |attr| attr.to_s }.sort
25
+ attributes.each do |attr|
26
+ self[dn][attr.to_sym].each do |value|
27
+ if attr == "userpassword" or value_is_binary?(value)
28
+ value = [value].pack("m").chomp.gsub(/\n/m, "\n ")
29
+ ary << "#{attr}:: #{value}"
30
+ else
31
+ ary << "#{attr}: #{value}"
32
+ end
33
+ end
34
+ end
35
+
36
+ ary << ""
37
+ end
38
+ block_given? and ary.each { |line| yield line}
39
+
40
+ ary
41
+ end
42
+
43
+ ##
44
+ # Outputs an LDAP Dataset as an LDIF string.
45
+ def to_ldif_string
46
+ to_ldif.join("\n")
47
+ end
48
+
49
+ ##
50
+ # Convert the parsed LDIF objects to Net::LDAP::Entry objects.
51
+ def to_entries
52
+ ary = []
53
+ keys.each do |dn|
54
+ entry = Net::LDAP::Entry.new(dn)
55
+ self[dn].each do |attr, value|
56
+ entry[attr] = value
57
+ end
58
+ ary << entry
59
+ end
60
+ ary
61
+ end
62
+
63
+ ##
64
+ # This is an internal convenience method to determine if a value requires
65
+ # base64-encoding before conversion to LDIF output. The standard approach
66
+ # in most LDAP tools is to check whether the value is a password, or if
67
+ # the first or last bytes are non-printable. Microsoft Active Directory,
68
+ # on the other hand, sometimes sends values that are binary in the middle.
69
+ #
70
+ # In the worst cases, this could be a nasty performance killer, which is
71
+ # why we handle the simplest cases first. Ideally, we would also test the
72
+ # first/last byte, but it's a bit harder to do this in a way that's
73
+ # compatible with both 1.8.6 and 1.8.7.
74
+ def value_is_binary?(value) # :nodoc:
75
+ value = value.to_s
76
+ return true if value[0] == ?: or value[0] == ?<
77
+ value.each_byte { |byte| return true if (byte < 32) || (byte > 126) }
78
+ false
79
+ end
80
+ private :value_is_binary?
81
+
82
+ class << self
83
+ class ChompedIO # :nodoc:
84
+ def initialize(io)
85
+ @io = io
86
+ end
87
+ def gets
88
+ s = @io.gets
89
+ s.chomp if s
90
+ end
91
+ end
92
+
93
+ ##
94
+ # Creates a Dataset object from an Entry object. Used mostly to assist
95
+ # with the conversion of
96
+ def from_entry(entry)
97
+ dataset = Net::LDAP::Dataset.new
98
+ hash = { }
99
+ entry.each_attribute do |attribute, value|
100
+ next if attribute == :dn
101
+ hash[attribute] = value
102
+ end
103
+ dataset[entry.dn] = hash
104
+ dataset
105
+ end
106
+
107
+ ##
108
+ # Reads an object that returns data line-wise (using #gets) and parses
109
+ # LDIF data into a Dataset object.
110
+ def read_ldif(io)
111
+ ds = Net::LDAP::Dataset.new
112
+ io = ChompedIO.new(io)
113
+
114
+ line = io.gets
115
+ dn = nil
116
+
117
+ while line
118
+ new_line = io.gets
119
+
120
+ if new_line =~ /^ /
121
+ line << $'
122
+ else
123
+ nextline = new_line
124
+
125
+ if line =~ /^#/
126
+ ds.comments << line
127
+ yield :comment, line if block_given?
128
+ elsif line =~ /^dn:[\s]*/i
129
+ dn = $'
130
+ ds[dn] = Hash.new { |k,v| k[v] = [] }
131
+ yield :dn, dn if block_given?
132
+ elsif line.empty?
133
+ dn = nil
134
+ yield :end, nil if block_given?
135
+ elsif line =~ /^([^:]+):([\:]?)[\s]*/
136
+ # $1 is the attribute name
137
+ # $2 is a colon iff the attr-value is base-64 encoded
138
+ # $' is the attr-value
139
+ # Avoid the Base64 class because not all Ruby versions have it.
140
+ attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
141
+ ds[dn][$1.downcase.to_sym] << attrvalue
142
+ yield :attr, [$1.downcase.to_sym, attrvalue] if block_given?
143
+ end
144
+
145
+ line = nextline
146
+ end
147
+ end
148
+
149
+ ds
150
+ end
151
+ end
152
+ end
153
+
154
+ require 'net/ldap/entry' unless defined? Net::LDAP::Entry
@@ -0,0 +1,225 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+
3
+ ##
4
+ # Objects of this class represent an LDAP DN ("Distinguished Name"). A DN
5
+ # ("Distinguished Name") is a unique identifier for an entry within an LDAP
6
+ # directory. It is made up of a number of other attributes strung together,
7
+ # to identify the entry in the tree.
8
+ #
9
+ # Each attribute that makes up a DN needs to have its value escaped so that
10
+ # the DN is valid. This class helps take care of that.
11
+ #
12
+ # A fully escaped DN needs to be unescaped when analysing its contents. This
13
+ # class also helps take care of that.
14
+ class Net::LDAP::DN
15
+ ##
16
+ # Initialize a DN, escaping as required. Pass in attributes in name/value
17
+ # pairs. If there is a left over argument, it will be appended to the dn
18
+ # without escaping (useful for a base string).
19
+ #
20
+ # Most uses of this class will be to escape a DN, rather than to parse it,
21
+ # so storing the dn as an escaped String and parsing parts as required
22
+ # with a state machine seems sensible.
23
+ def initialize(*args)
24
+ buffer = StringIO.new
25
+
26
+ args.each_index do |index|
27
+ buffer << "=" if index % 2 == 1
28
+ buffer << "," if index % 2 == 0 && index != 0
29
+
30
+ if index < args.length - 1 || index % 2 == 1
31
+ buffer << Net::LDAP::DN.escape(args[index])
32
+ else
33
+ buffer << args[index]
34
+ end
35
+ end
36
+
37
+ @dn = buffer.string
38
+ end
39
+
40
+ ##
41
+ # Parse a DN into key value pairs using ASN from
42
+ # http://tools.ietf.org/html/rfc2253 section 3.
43
+ def each_pair
44
+ state = :key
45
+ key = StringIO.new
46
+ value = StringIO.new
47
+ hex_buffer = ""
48
+
49
+ @dn.each_char do |char|
50
+ case state
51
+ when :key then
52
+ case char
53
+ when 'a'..'z', 'A'..'Z' then
54
+ state = :key_normal
55
+ key << char
56
+ when '0'..'9' then
57
+ state = :key_oid
58
+ key << char
59
+ when ' ' then state = :key
60
+ else raise "DN badly formed"
61
+ end
62
+ when :key_normal then
63
+ case char
64
+ when '=' then state = :value
65
+ when 'a'..'z', 'A'..'Z', '0'..'9', '-', ' ' then key << char
66
+ else raise "DN badly formed"
67
+ end
68
+ when :key_oid then
69
+ case char
70
+ when '=' then state = :value
71
+ when '0'..'9', '.', ' ' then key << char
72
+ else raise "DN badly formed"
73
+ end
74
+ when :value then
75
+ case char
76
+ when '\\' then state = :value_normal_escape
77
+ when '"' then state = :value_quoted
78
+ when ' ' then state = :value
79
+ when '#' then
80
+ state = :value_hexstring
81
+ value << char
82
+ when ',' then
83
+ state = :key
84
+ yield key.string.strip, value.string.rstrip
85
+ key = StringIO.new
86
+ value = StringIO.new;
87
+ else
88
+ state = :value_normal
89
+ value << char
90
+ end
91
+ when :value_normal then
92
+ case char
93
+ when '\\' then state = :value_normal_escape
94
+ when ',' then
95
+ state = :key
96
+ yield key.string.strip, value.string.rstrip
97
+ key = StringIO.new
98
+ value = StringIO.new;
99
+ else value << char
100
+ end
101
+ when :value_normal_escape then
102
+ case char
103
+ when '0'..'9', 'a'..'f', 'A'..'F' then
104
+ state = :value_normal_escape_hex
105
+ hex_buffer = char
106
+ else state = :value_normal; value << char
107
+ end
108
+ when :value_normal_escape_hex then
109
+ case char
110
+ when '0'..'9', 'a'..'f', 'A'..'F' then
111
+ state = :value_normal
112
+ value << "#{hex_buffer}#{char}".to_i(16).chr
113
+ else raise "DN badly formed"
114
+ end
115
+ when :value_quoted then
116
+ case char
117
+ when '\\' then state = :value_quoted_escape
118
+ when '"' then state = :value_end
119
+ else value << char
120
+ end
121
+ when :value_quoted_escape then
122
+ case char
123
+ when '0'..'9', 'a'..'f', 'A'..'F' then
124
+ state = :value_quoted_escape_hex
125
+ hex_buffer = char
126
+ else
127
+ state = :value_quoted;
128
+ value << char
129
+ end
130
+ when :value_quoted_escape_hex then
131
+ case char
132
+ when '0'..'9', 'a'..'f', 'A'..'F' then
133
+ state = :value_quoted
134
+ value << "#{hex_buffer}#{char}".to_i(16).chr
135
+ else raise "DN badly formed"
136
+ end
137
+ when :value_hexstring then
138
+ case char
139
+ when '0'..'9', 'a'..'f', 'A'..'F' then
140
+ state = :value_hexstring_hex
141
+ value << char
142
+ when ' ' then state = :value_end
143
+ when ',' then
144
+ state = :key
145
+ yield key.string.strip, value.string.rstrip
146
+ key = StringIO.new
147
+ value = StringIO.new;
148
+ else raise "DN badly formed"
149
+ end
150
+ when :value_hexstring_hex then
151
+ case char
152
+ when '0'..'9', 'a'..'f', 'A'..'F' then
153
+ state = :value_hexstring
154
+ value << char
155
+ else raise "DN badly formed"
156
+ end
157
+ when :value_end then
158
+ case char
159
+ when ' ' then state = :value_end
160
+ when ',' then
161
+ state = :key
162
+ yield key.string.strip, value.string.rstrip
163
+ key = StringIO.new
164
+ value = StringIO.new;
165
+ else raise "DN badly formed"
166
+ end
167
+ else raise "Fell out of state machine"
168
+ end
169
+ end
170
+
171
+ # Last pair
172
+ if [:value, :value_normal, :value_hexstring, :value_end].include? state
173
+ yield key.string.strip, value.string.rstrip
174
+ else
175
+ raise "DN badly formed"
176
+ end
177
+ end
178
+
179
+ ##
180
+ # Returns the DN as an array in the form expected by the constructor.
181
+ def to_a
182
+ a = []
183
+ self.each_pair { |key, value| a << key << value }
184
+ a
185
+ end
186
+
187
+ ##
188
+ # Return the DN as an escaped string.
189
+ def to_s
190
+ @dn
191
+ end
192
+
193
+ # http://tools.ietf.org/html/rfc2253 section 2.4 lists these exceptions
194
+ # for dn values. All of the following must be escaped in any normal string
195
+ # using a single backslash ('\') as escape.
196
+ ESCAPES = {
197
+ ',' => ',',
198
+ '+' => '+',
199
+ '"' => '"',
200
+ '\\' => '\\',
201
+ '<' => '<',
202
+ '>' => '>',
203
+ ';' => ';',
204
+ }
205
+
206
+ # Compiled character class regexp using the keys from the above hash, and
207
+ # checking for a space or # at the start, or space at the end, of the
208
+ # string.
209
+ ESCAPE_RE = Regexp.new("(^ |^#| $|[" +
210
+ ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
211
+ "])")
212
+
213
+ ##
214
+ # Escape a string for use in a DN value
215
+ def self.escape(string)
216
+ string.gsub(ESCAPE_RE) { |char| "\\" + ESCAPES[char] }
217
+ end
218
+
219
+ ##
220
+ # Proxy all other requests to the string object, because a DN is mainly
221
+ # used within the library as a string
222
+ def method_missing(method, *args, &block)
223
+ @dn.send(method, *args, &block)
224
+ end
225
+ end
@@ -0,0 +1,185 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+ ##
3
+ # Objects of this class represent individual entries in an LDAP directory.
4
+ # User code generally does not instantiate this class. Net::LDAP#search
5
+ # provides objects of this class to user code, either as block parameters or
6
+ # as return values.
7
+ #
8
+ # In LDAP-land, an "entry" is a collection of attributes that are uniquely
9
+ # and globally identified by a DN ("Distinguished Name"). Attributes are
10
+ # identified by short, descriptive words or phrases. Although a directory is
11
+ # free to implement any attribute name, most of them follow rigorous
12
+ # standards so that the range of commonly-encountered attribute names is not
13
+ # large.
14
+ #
15
+ # An attribute name is case-insensitive. Most directories also restrict the
16
+ # range of characters allowed in attribute names. To simplify handling
17
+ # attribute names, Net::LDAP::Entry internally converts them to a standard
18
+ # format. Therefore, the methods which take attribute names can take Strings
19
+ # or Symbols, and work correctly regardless of case or capitalization.
20
+ #
21
+ # An attribute consists of zero or more data items called <i>values.</i> An
22
+ # entry is the combination of a unique DN, a set of attribute names, and a
23
+ # (possibly-empty) array of values for each attribute.
24
+ #
25
+ # Class Net::LDAP::Entry provides convenience methods for dealing with LDAP
26
+ # entries. In addition to the methods documented below, you may access
27
+ # individual attributes of an entry simply by giving the attribute name as
28
+ # the name of a method call. For example:
29
+ #
30
+ # ldap.search( ... ) do |entry|
31
+ # puts "Common name: #{entry.cn}"
32
+ # puts "Email addresses:"
33
+ # entry.mail.each {|ma| puts ma}
34
+ # end
35
+ #
36
+ # If you use this technique to access an attribute that is not present in a
37
+ # particular Entry object, a NoMethodError exception will be raised.
38
+ #
39
+ #--
40
+ # Ugly problem to fix someday: We key off the internal hash with a canonical
41
+ # form of the attribute name: convert to a string, downcase, then take the
42
+ # symbol. Unfortunately we do this in at least three places. Should do it in
43
+ # ONE place.
44
+ class Net::LDAP::Entry
45
+ ##
46
+ # This constructor is not generally called by user code.
47
+ def initialize(dn = nil) #:nodoc:
48
+ @myhash = {}
49
+ @myhash[:dn] = [dn]
50
+ end
51
+
52
+ ##
53
+ # Use the LDIF format for Marshal serialization.
54
+ def _dump(depth) #:nodoc:
55
+ to_ldif
56
+ end
57
+
58
+ ##
59
+ # Use the LDIF format for Marshal serialization.
60
+ def self._load(entry) #:nodoc:
61
+ from_single_ldif_string(entry)
62
+ end
63
+
64
+ class << self
65
+ ##
66
+ # Converts a single LDIF entry string into an Entry object. Useful for
67
+ # Marshal serialization. If a string with multiple LDIF entries is
68
+ # provided, an exception will be raised.
69
+ def from_single_ldif_string(ldif)
70
+ ds = Net::LDAP::Dataset.read_ldif(::StringIO.new(ldif))
71
+
72
+ return nil if ds.empty?
73
+
74
+ raise Net::LDAP::LdapError, "Too many LDIF entries" unless ds.size == 1
75
+
76
+ entry = ds.to_entries.first
77
+
78
+ return nil if entry.dn.nil?
79
+ entry
80
+ end
81
+
82
+ ##
83
+ # Canonicalizes an LDAP attribute name as a \Symbol. The name is
84
+ # lowercased and, if present, a trailing equals sign is removed.
85
+ def attribute_name(name)
86
+ name = name.to_s.downcase
87
+ name = name[0..-2] if name[-1] == ?=
88
+ name.to_sym
89
+ end
90
+ end
91
+
92
+ ##
93
+ # Sets or replaces the array of values for the provided attribute. The
94
+ # attribute name is canonicalized prior to assignment.
95
+ #
96
+ # When an attribute is set using this, that attribute is now made
97
+ # accessible through methods as well.
98
+ #
99
+ # entry = Net::LDAP::Entry.new("dc=com")
100
+ # entry.foo # => NoMethodError
101
+ # entry["foo"] = 12345 # => [12345]
102
+ # entry.foo # => [12345]
103
+ def []=(name, value)
104
+ @myhash[self.class.attribute_name(name)] = Kernel::Array(value)
105
+ end
106
+
107
+ ##
108
+ # Reads the array of values for the provided attribute. The attribute name
109
+ # is canonicalized prior to reading. Returns an empty array if the
110
+ # attribute does not exist.
111
+ def [](name)
112
+ name = self.class.attribute_name(name)
113
+ @myhash[name] || []
114
+ end
115
+
116
+ ##
117
+ # Returns the first distinguished name (dn) of the Entry as a \String.
118
+ def dn
119
+ self[:dn].first.to_s
120
+ end
121
+
122
+ ##
123
+ # Returns an array of the attribute names present in the Entry.
124
+ def attribute_names
125
+ @myhash.keys
126
+ end
127
+
128
+ ##
129
+ # Accesses each of the attributes present in the Entry.
130
+ #
131
+ # Calls a user-supplied block with each attribute in turn, passing two
132
+ # arguments to the block: a Symbol giving the name of the attribute, and a
133
+ # (possibly empty) \Array of data values.
134
+ def each # :yields: attribute-name, data-values-array
135
+ if block_given?
136
+ attribute_names.each {|a|
137
+ attr_name,values = a,self[a]
138
+ yield attr_name, values
139
+ }
140
+ end
141
+ end
142
+ alias_method :each_attribute, :each
143
+
144
+ ##
145
+ # Converts the Entry to an LDIF-formatted String
146
+ def to_ldif
147
+ Net::LDAP::Dataset.from_entry(self).to_ldif_string
148
+ end
149
+
150
+ def respond_to?(sym) #:nodoc:
151
+ return true if valid_attribute?(self.class.attribute_name(sym))
152
+ return super
153
+ end
154
+
155
+ def method_missing(sym, *args, &block) #:nodoc:
156
+ name = self.class.attribute_name(sym)
157
+
158
+ if valid_attribute?(name )
159
+ if setter?(sym) && args.size == 1
160
+ value = args.first
161
+ value = Array(value)
162
+ self[name]= value
163
+ return value
164
+ elsif args.empty?
165
+ return self[name]
166
+ end
167
+ end
168
+
169
+ super
170
+ end
171
+
172
+ # Given a valid attribute symbol, returns true.
173
+ def valid_attribute?(attr_name)
174
+ attribute_names.include?(attr_name)
175
+ end
176
+ private :valid_attribute?
177
+
178
+ # Returns true if the symbol ends with an equal sign.
179
+ def setter?(sym)
180
+ sym.to_s[-1] == ?=
181
+ end
182
+ private :setter?
183
+ end # class Entry
184
+
185
+ require 'net/ldap/dataset' unless defined? Net::LDAP::Dataset