prathe-net-ldap 0.2.20110317223538

Sign up to get free protection for your applications and to get access to all the features.
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