net-ldap 0.1.1 → 0.2
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.
Potentially problematic release.
This version of net-ldap might be problematic. Click here for more details.
- data/.autotest +11 -0
- data/.gemtest +0 -0
- data/.rspec +2 -0
- data/Contributors.rdoc +21 -0
- data/Hacking.rdoc +68 -0
- data/{History.txt → History.rdoc} +65 -1
- data/License.rdoc +29 -0
- data/Manifest.txt +25 -14
- data/README.rdoc +52 -0
- data/Rakefile +52 -96
- data/autotest/discover.rb +1 -0
- data/lib/net-ldap.rb +1 -0
- data/lib/net/ber.rb +302 -81
- data/lib/net/ber/ber_parser.rb +153 -97
- data/lib/net/ber/core_ext.rb +62 -0
- data/lib/net/ber/core_ext/array.rb +82 -0
- data/lib/net/ber/core_ext/bignum.rb +22 -0
- data/lib/net/ber/core_ext/false_class.rb +10 -0
- data/lib/net/ber/core_ext/fixnum.rb +66 -0
- data/lib/net/ber/core_ext/string.rb +48 -0
- data/lib/net/ber/core_ext/true_class.rb +12 -0
- data/lib/net/ldap.rb +1455 -1475
- data/lib/net/ldap/dataset.rb +134 -79
- data/lib/net/ldap/dn.rb +225 -0
- data/lib/net/ldap/entry.rb +168 -249
- data/lib/net/ldap/filter.rb +654 -387
- data/lib/net/ldap/password.rb +31 -0
- data/lib/net/ldap/pdu.rb +232 -233
- data/lib/net/snmp.rb +4 -31
- data/net-ldap.gemspec +59 -0
- data/spec/integration/ssl_ber_spec.rb +3 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/unit/ber/ber_spec.rb +82 -6
- data/spec/unit/ber/core_ext/string_spec.rb +51 -0
- data/spec/unit/ldap/dn_spec.rb +80 -0
- data/spec/unit/ldap/entry_spec.rb +51 -0
- data/spec/unit/ldap/filter_spec.rb +84 -0
- data/spec/unit/ldap_spec.rb +48 -0
- data/test/test_entry.rb +54 -2
- data/test/test_filter.rb +93 -54
- data/test/test_ldap_connection.rb +24 -0
- data/test/test_ldif.rb +31 -23
- data/test/test_rename.rb +77 -0
- data/test/test_snmp.rb +34 -33
- metadata +88 -52
- data/COPYING +0 -272
- data/LICENSE +0 -56
- data/README.txt +0 -68
- data/lib/net/ldap/core_ext/all.rb +0 -43
- data/lib/net/ldap/core_ext/array.rb +0 -42
- data/lib/net/ldap/core_ext/bignum.rb +0 -25
- data/lib/net/ldap/core_ext/false_class.rb +0 -11
- data/lib/net/ldap/core_ext/fixnum.rb +0 -74
- data/lib/net/ldap/core_ext/string.rb +0 -40
- data/lib/net/ldap/core_ext/true_class.rb +0 -11
- data/lib/net/ldap/psw.rb +0 -57
- data/lib/net/ldif.rb +0 -34
- data/test/test_ber.rb +0 -78
data/lib/net/ldap/dataset.rb
CHANGED
@@ -1,99 +1,154 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
#
|
8
|
-
|
9
|
-
# the Free Software Foundation; either version 2 of the License, or
|
10
|
-
# (at your option) any later version.
|
11
|
-
#
|
12
|
-
# This program is distributed in the hope that it will be useful,
|
13
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
-
# GNU General Public License for more details.
|
16
|
-
#
|
17
|
-
# You should have received a copy of the GNU General Public License
|
18
|
-
# along with this program; if not, write to the Free Software
|
19
|
-
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
20
|
-
#
|
21
|
-
#---------------------------------------------------------------------------
|
22
|
-
|
23
|
-
module Net
|
24
|
-
class LDAP
|
25
|
-
class Dataset < Hash
|
26
|
-
attr_reader :comments
|
27
|
-
|
28
|
-
class IOFilter
|
29
|
-
def initialize(io)
|
30
|
-
@io = io
|
31
|
-
end
|
32
|
-
def gets
|
33
|
-
s = @io.gets
|
34
|
-
s.chomp if s
|
35
|
-
end
|
36
|
-
end
|
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
|
37
9
|
|
38
|
-
|
39
|
-
|
40
|
-
|
10
|
+
def initialize(*args, &block) # :nodoc:
|
11
|
+
super
|
12
|
+
@comments = []
|
13
|
+
end
|
41
14
|
|
42
|
-
|
43
|
-
|
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}"
|
44
23
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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}"
|
49
30
|
else
|
50
|
-
|
51
|
-
|
52
|
-
if line =~ /^\#/
|
53
|
-
ds.comments << line
|
54
|
-
elsif line =~ /^dn:[\s]*/i
|
55
|
-
dn = $'
|
56
|
-
ds[dn] = Hash.new {|k,v| k[v] = []}
|
57
|
-
elsif line.length == 0
|
58
|
-
dn = nil
|
59
|
-
elsif line =~ /^([^:]+):([\:]?)[\s]*/
|
60
|
-
# $1 is the attribute name
|
61
|
-
# $2 is a colon iff the attr-value is base-64 encoded
|
62
|
-
# $' is the attr-value
|
63
|
-
# Avoid the Base64 class because not all Ruby versions have it.
|
64
|
-
attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
|
65
|
-
ds[dn][$1.downcase.intern] << attrvalue
|
66
|
-
end
|
67
|
-
|
68
|
-
line = nextline
|
31
|
+
ary << "#{attr}: #{value}"
|
69
32
|
end
|
70
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
|
71
48
|
|
72
|
-
|
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
|
73
57
|
end
|
58
|
+
ary << entry
|
59
|
+
end
|
60
|
+
ary
|
61
|
+
end
|
74
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?
|
75
81
|
|
76
|
-
|
77
|
-
|
82
|
+
class << self
|
83
|
+
class ChompedIO # :nodoc:
|
84
|
+
def initialize(io)
|
85
|
+
@io = io
|
78
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
|
79
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)
|
80
113
|
|
81
|
-
|
82
|
-
|
83
|
-
ary += (@comments || [])
|
84
|
-
keys.sort.each do |dn|
|
85
|
-
ary << "dn: #{dn}"
|
114
|
+
line = io.gets
|
115
|
+
dn = nil
|
86
116
|
|
87
|
-
|
88
|
-
|
117
|
+
while line
|
118
|
+
new_line = io.gets
|
119
|
+
|
120
|
+
if new_line =~ /^[\s]+/
|
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?
|
89
143
|
end
|
90
144
|
|
91
|
-
|
145
|
+
line = nextline
|
92
146
|
end
|
93
|
-
block_given? and ary.each {|line| yield line}
|
94
|
-
ary
|
95
147
|
end
|
96
148
|
|
97
|
-
|
98
|
-
|
149
|
+
ds
|
150
|
+
end
|
151
|
+
end
|
99
152
|
end
|
153
|
+
|
154
|
+
require 'net/ldap/entry' unless defined? Net::LDAP::Entry
|
data/lib/net/ldap/dn.rb
ADDED
@@ -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
|