net-ldap 0.11 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of net-ldap might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rubocop.yml +15 -0
- data/.rubocop_todo.yml +471 -180
- data/.travis.yml +10 -5
- data/Contributors.rdoc +1 -0
- data/History.rdoc +60 -0
- data/README.rdoc +18 -11
- data/Rakefile +0 -1
- data/lib/net/ber/ber_parser.rb +4 -4
- data/lib/net/ber/core_ext/array.rb +1 -1
- data/lib/net/ber/core_ext/integer.rb +1 -1
- data/lib/net/ber/core_ext/string.rb +1 -1
- data/lib/net/ber.rb +37 -5
- data/lib/net/ldap/auth_adapter/gss_spnego.rb +41 -0
- data/lib/net/ldap/auth_adapter/sasl.rb +62 -0
- data/lib/net/ldap/auth_adapter/simple.rb +34 -0
- data/lib/net/ldap/auth_adapter.rb +29 -0
- data/lib/net/ldap/connection.rb +197 -187
- data/lib/net/ldap/dataset.rb +2 -2
- data/lib/net/ldap/dn.rb +4 -5
- data/lib/net/ldap/entry.rb +4 -5
- data/lib/net/ldap/error.rb +36 -1
- data/lib/net/ldap/filter.rb +6 -6
- data/lib/net/ldap/pdu.rb +26 -2
- data/lib/net/ldap/version.rb +1 -1
- data/lib/net/ldap.rb +189 -75
- data/lib/net/snmp.rb +18 -18
- data/net-ldap.gemspec +4 -2
- data/script/changelog +47 -0
- data/script/generate-fixture-ca +48 -0
- data/script/install-openldap +67 -44
- data/test/ber/core_ext/test_array.rb +1 -1
- data/test/ber/test_ber.rb +11 -3
- data/test/fixtures/ca/ca.info +4 -0
- data/test/fixtures/ca/cacert.pem +24 -0
- data/test/fixtures/ca/cakey.pem +190 -0
- data/test/fixtures/openldap/slapd.conf.ldif +1 -1
- data/test/integration/test_add.rb +1 -1
- data/test/integration/test_ber.rb +1 -1
- data/test/integration/test_bind.rb +220 -10
- data/test/integration/test_delete.rb +1 -1
- data/test/integration/test_open.rb +1 -1
- data/test/integration/test_password_modify.rb +80 -0
- data/test/integration/test_search.rb +1 -1
- data/test/support/vm/openldap/README.md +35 -3
- data/test/support/vm/openldap/Vagrantfile +1 -0
- data/test/test_auth_adapter.rb +15 -0
- data/test/test_dn.rb +3 -3
- data/test/test_filter.rb +4 -4
- data/test/test_filter_parser.rb +4 -0
- data/test/test_helper.rb +10 -2
- data/test/test_ldap.rb +64 -10
- data/test/test_ldap_connection.rb +115 -28
- data/test/test_ldif.rb +11 -11
- data/test/test_search.rb +2 -2
- data/test/test_snmp.rb +4 -4
- data/testserver/ldapserver.rb +11 -12
- metadata +50 -8
- data/test/fixtures/cacert.pem +0 -20
data/lib/net/ldap/connection.rb
CHANGED
@@ -3,29 +3,73 @@
|
|
3
3
|
class Net::LDAP::Connection #:nodoc:
|
4
4
|
include Net::LDAP::Instrumentation
|
5
5
|
|
6
|
+
# Seconds before failing for socket connect timeout
|
7
|
+
DefaultConnectTimeout = 5
|
8
|
+
|
6
9
|
LdapVersion = 3
|
7
|
-
MaxSaslChallenges = 10
|
8
10
|
|
9
|
-
|
11
|
+
# Initialize a connection to an LDAP server
|
12
|
+
#
|
13
|
+
# :server
|
14
|
+
# :hosts Array of tuples specifying host, port
|
15
|
+
# :host host
|
16
|
+
# :port port
|
17
|
+
# :socket prepared socket
|
18
|
+
#
|
19
|
+
def initialize(server = {})
|
20
|
+
@server = server
|
10
21
|
@instrumentation_service = server[:instrumentation_service]
|
11
22
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
+
# Allows tests to parameterize what socket class to use
|
24
|
+
@socket_class = server.fetch(:socket_class, DefaultSocket)
|
25
|
+
|
26
|
+
yield self if block_given?
|
27
|
+
end
|
28
|
+
|
29
|
+
def socket_class=(socket_class)
|
30
|
+
@socket_class = socket_class
|
31
|
+
end
|
32
|
+
|
33
|
+
def prepare_socket(server, timeout=nil)
|
34
|
+
socket = server[:socket]
|
35
|
+
encryption = server[:encryption]
|
36
|
+
|
37
|
+
@conn = socket
|
38
|
+
setup_encryption(encryption, timeout) if encryption
|
39
|
+
end
|
40
|
+
|
41
|
+
def open_connection(server)
|
42
|
+
hosts = server[:hosts]
|
43
|
+
encryption = server[:encryption]
|
44
|
+
|
45
|
+
timeout = server[:connect_timeout] || DefaultConnectTimeout
|
46
|
+
socket_opts = {
|
47
|
+
connect_timeout: timeout,
|
48
|
+
}
|
23
49
|
|
24
|
-
|
25
|
-
|
50
|
+
errors = []
|
51
|
+
hosts.each do |host, port|
|
52
|
+
begin
|
53
|
+
prepare_socket(server.merge(socket: @socket_class.new(host, port, socket_opts)), timeout)
|
54
|
+
if encryption
|
55
|
+
if encryption[:tls_options] &&
|
56
|
+
encryption[:tls_options][:verify_mode] &&
|
57
|
+
encryption[:tls_options][:verify_mode] == OpenSSL::SSL::VERIFY_NONE
|
58
|
+
warn "not verifying SSL hostname of LDAPS server '#{host}:#{port}'"
|
59
|
+
else
|
60
|
+
@conn.post_connection_check(host)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
return
|
64
|
+
rescue Net::LDAP::Error, SocketError, SystemCallError,
|
65
|
+
OpenSSL::SSL::SSLError => e
|
66
|
+
# Ensure the connection is closed in the event a setup failure.
|
67
|
+
close
|
68
|
+
errors << [e, host, port]
|
69
|
+
end
|
26
70
|
end
|
27
71
|
|
28
|
-
|
72
|
+
raise Net::LDAP::ConnectionError.new(errors)
|
29
73
|
end
|
30
74
|
|
31
75
|
module GetbyteForSSLSocket
|
@@ -41,7 +85,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
41
85
|
end
|
42
86
|
end
|
43
87
|
|
44
|
-
def self.wrap_with_ssl(io, tls_options = {})
|
88
|
+
def self.wrap_with_ssl(io, tls_options = {}, timeout=nil)
|
45
89
|
raise Net::LDAP::NoOpenSSLError, "OpenSSL is unavailable" unless Net::LDAP::HasOpenSSL
|
46
90
|
|
47
91
|
ctx = OpenSSL::SSL::SSLContext.new
|
@@ -51,7 +95,22 @@ class Net::LDAP::Connection #:nodoc:
|
|
51
95
|
ctx.set_params(tls_options) unless tls_options.empty?
|
52
96
|
|
53
97
|
conn = OpenSSL::SSL::SSLSocket.new(io, ctx)
|
54
|
-
|
98
|
+
|
99
|
+
begin
|
100
|
+
if timeout
|
101
|
+
conn.connect_nonblock
|
102
|
+
else
|
103
|
+
conn.connect
|
104
|
+
end
|
105
|
+
rescue IO::WaitReadable
|
106
|
+
raise Errno::ETIMEDOUT, "OpenSSL connection read timeout" unless
|
107
|
+
IO.select([conn], nil, nil, timeout)
|
108
|
+
retry
|
109
|
+
rescue IO::WaitWritable
|
110
|
+
raise Errno::ETIMEDOUT, "OpenSSL connection write timeout" unless
|
111
|
+
IO.select(nil, [conn], nil, timeout)
|
112
|
+
retry
|
113
|
+
end
|
55
114
|
|
56
115
|
# Doesn't work:
|
57
116
|
# conn.sync_close = true
|
@@ -63,18 +122,18 @@ class Net::LDAP::Connection #:nodoc:
|
|
63
122
|
end
|
64
123
|
|
65
124
|
#--
|
66
|
-
# Helper method called only from
|
67
|
-
# successfully-opened @conn instance variable, which is a TCP
|
68
|
-
# Depending on the received arguments, we establish SSL,
|
69
|
-
# replacing the value of @conn accordingly. Don't generate any
|
70
|
-
# if no encryption is requested. DO raise Net::LDAP::Error objects
|
71
|
-
# is requested and we have trouble setting it up. That includes
|
72
|
-
# is not set up on the machine. (Question: how does the Ruby
|
73
|
-
# wrapper react in that case?) DO NOT filter exceptions raised by the
|
74
|
-
# OpenSSL library. Let them pass back to the user. That should make it
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
125
|
+
# Helper method called only from prepare_socket or open_connection, and only
|
126
|
+
# after we have a successfully-opened @conn instance variable, which is a TCP
|
127
|
+
# connection. Depending on the received arguments, we establish SSL,
|
128
|
+
# potentially replacing the value of @conn accordingly. Don't generate any
|
129
|
+
# errors here if no encryption is requested. DO raise Net::LDAP::Error objects
|
130
|
+
# if encryption is requested and we have trouble setting it up. That includes
|
131
|
+
# if OpenSSL is not set up on the machine. (Question: how does the Ruby
|
132
|
+
# OpenSSL wrapper react in that case?) DO NOT filter exceptions raised by the
|
133
|
+
# OpenSSL library. Let them pass back to the user. That should make it easier
|
134
|
+
# for us to debug the problem reports. Presumably (hopefully?) that will also
|
135
|
+
# produce recognizable errors if someone tries to use this on a machine
|
136
|
+
# without OpenSSL.
|
78
137
|
#
|
79
138
|
# The simple_tls method is intended as the simplest, stupidest, easiest
|
80
139
|
# solution for people who want nothing more than encrypted comms with the
|
@@ -88,17 +147,17 @@ class Net::LDAP::Connection #:nodoc:
|
|
88
147
|
# communications, as with simple_tls. Thanks for Kouhei Sutou for
|
89
148
|
# generously contributing the :start_tls path.
|
90
149
|
#++
|
91
|
-
def setup_encryption(args)
|
150
|
+
def setup_encryption(args, timeout=nil)
|
92
151
|
args[:tls_options] ||= {}
|
93
152
|
case args[:method]
|
94
153
|
when :simple_tls
|
95
|
-
@conn = self.class.wrap_with_ssl(@conn, args[:tls_options])
|
154
|
+
@conn = self.class.wrap_with_ssl(@conn, args[:tls_options], timeout)
|
96
155
|
# additional branches requiring server validation and peer certs, etc.
|
97
156
|
# go here.
|
98
157
|
when :start_tls
|
99
158
|
message_id = next_msgid
|
100
159
|
request = [
|
101
|
-
Net::LDAP::StartTlsOid.to_ber_contextspecific(0)
|
160
|
+
Net::LDAP::StartTlsOid.to_ber_contextspecific(0),
|
102
161
|
].to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
|
103
162
|
|
104
163
|
write(request, nil, message_id)
|
@@ -108,11 +167,9 @@ class Net::LDAP::Connection #:nodoc:
|
|
108
167
|
raise Net::LDAP::NoStartTLSResultError, "no start_tls result"
|
109
168
|
end
|
110
169
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
raise Net::LDAP::StartTlSError, "start_tls failed: #{pdu.result_code}"
|
115
|
-
end
|
170
|
+
raise Net::LDAP::StartTLSError,
|
171
|
+
"start_tls failed: #{pdu.result_code}" unless pdu.result_code.zero?
|
172
|
+
@conn = self.class.wrap_with_ssl(@conn, args[:tls_options], timeout)
|
116
173
|
else
|
117
174
|
raise Net::LDAP::EncMethodUnsupportedError, "unsupported encryption method #{args[:method]}"
|
118
175
|
end
|
@@ -124,6 +181,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
124
181
|
# have to call it, but perhaps it will come in handy someday.
|
125
182
|
#++
|
126
183
|
def close
|
184
|
+
return if @conn.nil?
|
127
185
|
@conn.close
|
128
186
|
@conn = nil
|
129
187
|
end
|
@@ -141,12 +199,10 @@ class Net::LDAP::Connection #:nodoc:
|
|
141
199
|
|
142
200
|
# read messages until we have a match for the given message_id
|
143
201
|
while pdu = read
|
144
|
-
if pdu.message_id == message_id
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
next
|
149
|
-
end
|
202
|
+
return pdu if pdu.message_id == message_id
|
203
|
+
|
204
|
+
message_queue[pdu.message_id].push pdu
|
205
|
+
next
|
150
206
|
end
|
151
207
|
|
152
208
|
pdu
|
@@ -175,7 +231,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
175
231
|
def read(syntax = Net::LDAP::AsnSyntax)
|
176
232
|
ber_object =
|
177
233
|
instrument "read.net_ldap_connection", :syntax => syntax do |payload|
|
178
|
-
|
234
|
+
socket.read_ber(syntax) do |id, content_length|
|
179
235
|
payload[:object_type_id] = id
|
180
236
|
payload[:content_length] = content_length
|
181
237
|
end
|
@@ -205,7 +261,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
205
261
|
def write(request, controls = nil, message_id = next_msgid)
|
206
262
|
instrument "write.net_ldap_connection" do |payload|
|
207
263
|
packet = [message_id.to_ber, request, controls].compact.to_ber_sequence
|
208
|
-
payload[:content_length] =
|
264
|
+
payload[:content_length] = socket.write(packet)
|
209
265
|
end
|
210
266
|
end
|
211
267
|
private :write
|
@@ -218,130 +274,11 @@ class Net::LDAP::Connection #:nodoc:
|
|
218
274
|
def bind(auth)
|
219
275
|
instrument "bind.net_ldap_connection" do |payload|
|
220
276
|
payload[:method] = meth = auth[:method]
|
221
|
-
|
222
|
-
|
223
|
-
elsif meth == :sasl
|
224
|
-
bind_sasl(auth)
|
225
|
-
elsif meth == :gss_spnego
|
226
|
-
bind_gss_spnego(auth)
|
227
|
-
else
|
228
|
-
raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (#{meth})"
|
229
|
-
end
|
277
|
+
adapter = Net::LDAP::AuthAdapter[meth]
|
278
|
+
adapter.new(self).bind(auth)
|
230
279
|
end
|
231
280
|
end
|
232
281
|
|
233
|
-
#--
|
234
|
-
# Implements a simple user/psw authentication. Accessed by calling #bind
|
235
|
-
# with a method of :simple or :anonymous.
|
236
|
-
#++
|
237
|
-
def bind_simple(auth)
|
238
|
-
user, psw = if auth[:method] == :simple
|
239
|
-
[auth[:username] || auth[:dn], auth[:password]]
|
240
|
-
else
|
241
|
-
["", ""]
|
242
|
-
end
|
243
|
-
|
244
|
-
raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
|
245
|
-
|
246
|
-
message_id = next_msgid
|
247
|
-
request = [
|
248
|
-
LdapVersion.to_ber, user.to_ber,
|
249
|
-
psw.to_ber_contextspecific(0)
|
250
|
-
].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
|
251
|
-
|
252
|
-
write(request, nil, message_id)
|
253
|
-
pdu = queued_read(message_id)
|
254
|
-
|
255
|
-
if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
|
256
|
-
raise Net::LDAP::NoBindResultError, "no bind result"
|
257
|
-
end
|
258
|
-
|
259
|
-
pdu
|
260
|
-
end
|
261
|
-
|
262
|
-
#--
|
263
|
-
# Required parameters: :mechanism, :initial_credential and
|
264
|
-
# :challenge_response
|
265
|
-
#
|
266
|
-
# Mechanism is a string value that will be passed in the SASL-packet's
|
267
|
-
# "mechanism" field.
|
268
|
-
#
|
269
|
-
# Initial credential is most likely a string. It's passed in the initial
|
270
|
-
# BindRequest that goes to the server. In some protocols, it may be empty.
|
271
|
-
#
|
272
|
-
# Challenge-response is a Ruby proc that takes a single parameter and
|
273
|
-
# returns an object that will typically be a string. The
|
274
|
-
# challenge-response block is called when the server returns a
|
275
|
-
# BindResponse with a result code of 14 (saslBindInProgress). The
|
276
|
-
# challenge-response block receives a parameter containing the data
|
277
|
-
# returned by the server in the saslServerCreds field of the LDAP
|
278
|
-
# BindResponse packet. The challenge-response block may be called multiple
|
279
|
-
# times during the course of a SASL authentication, and each time it must
|
280
|
-
# return a value that will be passed back to the server as the credential
|
281
|
-
# data in the next BindRequest packet.
|
282
|
-
#++
|
283
|
-
def bind_sasl(auth)
|
284
|
-
mech, cred, chall = auth[:mechanism], auth[:initial_credential],
|
285
|
-
auth[:challenge_response]
|
286
|
-
raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (mech && cred && chall)
|
287
|
-
|
288
|
-
message_id = next_msgid
|
289
|
-
|
290
|
-
n = 0
|
291
|
-
loop {
|
292
|
-
sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)
|
293
|
-
request = [
|
294
|
-
LdapVersion.to_ber, "".to_ber, sasl
|
295
|
-
].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
|
296
|
-
|
297
|
-
write(request, nil, message_id)
|
298
|
-
pdu = queued_read(message_id)
|
299
|
-
|
300
|
-
if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
|
301
|
-
raise Net::LDAP::NoBindResultError, "no bind result"
|
302
|
-
end
|
303
|
-
|
304
|
-
return pdu unless pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress
|
305
|
-
raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges)
|
306
|
-
|
307
|
-
cred = chall.call(pdu.result_server_sasl_creds)
|
308
|
-
}
|
309
|
-
|
310
|
-
raise Net::LDAP::SASLChallengeOverflowError, "why are we here?"
|
311
|
-
end
|
312
|
-
private :bind_sasl
|
313
|
-
|
314
|
-
#--
|
315
|
-
# PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET.
|
316
|
-
# Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to
|
317
|
-
# integrate it without introducing an external dependency.
|
318
|
-
#
|
319
|
-
# This authentication method is accessed by calling #bind with a :method
|
320
|
-
# parameter of :gss_spnego. It requires :username and :password
|
321
|
-
# attributes, just like the :simple authentication method. It performs a
|
322
|
-
# GSS-SPNEGO authentication with the server, which is presumed to be a
|
323
|
-
# Microsoft Active Directory.
|
324
|
-
#++
|
325
|
-
def bind_gss_spnego(auth)
|
326
|
-
require 'ntlm'
|
327
|
-
|
328
|
-
user, psw = [auth[:username] || auth[:dn], auth[:password]]
|
329
|
-
raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
|
330
|
-
|
331
|
-
nego = proc { |challenge|
|
332
|
-
t2_msg = NTLM::Message.parse(challenge)
|
333
|
-
t3_msg = t2_msg.response({ :user => user, :password => psw },
|
334
|
-
{ :ntlmv2 => true })
|
335
|
-
t3_msg.serialize
|
336
|
-
}
|
337
|
-
|
338
|
-
bind_sasl(:method => :sasl, :mechanism => "GSS-SPNEGO",
|
339
|
-
:initial_credential => NTLM::Message::Type1.new.serialize,
|
340
|
-
:challenge_response => nego)
|
341
|
-
end
|
342
|
-
private :bind_gss_spnego
|
343
|
-
|
344
|
-
|
345
282
|
#--
|
346
283
|
# Allow the caller to specify a sort control
|
347
284
|
#
|
@@ -366,7 +303,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
366
303
|
sort_control = [
|
367
304
|
Net::LDAP::LDAPControls::SORT_REQUEST.to_ber,
|
368
305
|
false.to_ber,
|
369
|
-
sort_control_values.to_ber_sequence.to_s.to_ber
|
306
|
+
sort_control_values.to_ber_sequence.to_s.to_ber,
|
370
307
|
].to_ber_sequence
|
371
308
|
end
|
372
309
|
|
@@ -463,12 +400,11 @@ class Net::LDAP::Connection #:nodoc:
|
|
463
400
|
# should collect this into a private helper to clarify the structure
|
464
401
|
query_limit = 0
|
465
402
|
if size > 0
|
466
|
-
if paged
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
end
|
403
|
+
query_limit = if paged
|
404
|
+
(((size - n_results) < 126) ? (size - n_results) : 0)
|
405
|
+
else
|
406
|
+
size
|
407
|
+
end
|
472
408
|
end
|
473
409
|
|
474
410
|
request = [
|
@@ -479,7 +415,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
479
415
|
time.to_ber,
|
480
416
|
attrs_only.to_ber,
|
481
417
|
filter.to_ber,
|
482
|
-
ber_attrs.to_ber_sequence
|
418
|
+
ber_attrs.to_ber_sequence,
|
483
419
|
].to_ber_appsequence(Net::LDAP::PDU::SearchRequest)
|
484
420
|
|
485
421
|
# rfc2696_cookie sometimes contains binary data from Microsoft Active Directory
|
@@ -492,7 +428,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
492
428
|
Net::LDAP::LDAPControls::PAGED_RESULTS.to_ber,
|
493
429
|
# Criticality MUST be false to interoperate with normal LDAPs.
|
494
430
|
false.to_ber,
|
495
|
-
rfc2696_cookie.map
|
431
|
+
rfc2696_cookie.map(&:to_ber).to_ber_sequence.to_s.to_ber,
|
496
432
|
].to_ber_sequence if paged
|
497
433
|
controls << ber_sort if ber_sort
|
498
434
|
controls = controls.empty? ? nil : controls.to_ber_contextspecific(0)
|
@@ -586,20 +522,20 @@ class Net::LDAP::Connection #:nodoc:
|
|
586
522
|
MODIFY_OPERATIONS = { #:nodoc:
|
587
523
|
:add => 0,
|
588
524
|
:delete => 1,
|
589
|
-
:replace => 2
|
525
|
+
:replace => 2,
|
590
526
|
}
|
591
527
|
|
592
528
|
def self.modify_ops(operations)
|
593
529
|
ops = []
|
594
530
|
if operations
|
595
|
-
operations.each
|
531
|
+
operations.each do |op, attrib, values|
|
596
532
|
# TODO, fix the following line, which gives a bogus error if the
|
597
533
|
# opcode is invalid.
|
598
534
|
op_ber = MODIFY_OPERATIONS[op.to_sym].to_ber_enumerated
|
599
|
-
values = [
|
600
|
-
values = [
|
601
|
-
ops << [
|
602
|
-
|
535
|
+
values = [values].flatten.map { |v| v.to_ber if v }.to_ber_set
|
536
|
+
values = [attrib.to_s.to_ber, values].to_ber_sequence
|
537
|
+
ops << [op_ber, values].to_ber
|
538
|
+
end
|
603
539
|
end
|
604
540
|
ops
|
605
541
|
end
|
@@ -618,7 +554,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
618
554
|
message_id = next_msgid
|
619
555
|
request = [
|
620
556
|
modify_dn.to_ber,
|
621
|
-
ops.to_ber_sequence
|
557
|
+
ops.to_ber_sequence,
|
622
558
|
].to_ber_appsequence(Net::LDAP::PDU::ModifyRequest)
|
623
559
|
|
624
560
|
write(request, nil, message_id)
|
@@ -631,6 +567,51 @@ class Net::LDAP::Connection #:nodoc:
|
|
631
567
|
pdu
|
632
568
|
end
|
633
569
|
|
570
|
+
##
|
571
|
+
# Password Modify
|
572
|
+
#
|
573
|
+
# http://tools.ietf.org/html/rfc3062
|
574
|
+
#
|
575
|
+
# passwdModifyOID OBJECT IDENTIFIER ::= 1.3.6.1.4.1.4203.1.11.1
|
576
|
+
#
|
577
|
+
# PasswdModifyRequestValue ::= SEQUENCE {
|
578
|
+
# userIdentity [0] OCTET STRING OPTIONAL
|
579
|
+
# oldPasswd [1] OCTET STRING OPTIONAL
|
580
|
+
# newPasswd [2] OCTET STRING OPTIONAL }
|
581
|
+
#
|
582
|
+
# PasswdModifyResponseValue ::= SEQUENCE {
|
583
|
+
# genPasswd [0] OCTET STRING OPTIONAL }
|
584
|
+
#
|
585
|
+
# Encoded request:
|
586
|
+
#
|
587
|
+
# 00\x02\x01\x02w+\x80\x171.3.6.1.4.1.4203.1.11.1\x81\x100\x0E\x81\x05old\x82\x05new
|
588
|
+
#
|
589
|
+
def password_modify(args)
|
590
|
+
dn = args[:dn]
|
591
|
+
raise ArgumentError, 'DN is required' if !dn || dn.empty?
|
592
|
+
|
593
|
+
ext_seq = [Net::LDAP::PasswdModifyOid.to_ber_contextspecific(0)]
|
594
|
+
|
595
|
+
unless args[:old_password].nil?
|
596
|
+
pwd_seq = [args[:old_password].to_ber(0x81)]
|
597
|
+
pwd_seq << args[:new_password].to_ber(0x82) unless args[:new_password].nil?
|
598
|
+
ext_seq << pwd_seq.to_ber_sequence.to_ber(0x81)
|
599
|
+
end
|
600
|
+
|
601
|
+
request = ext_seq.to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
|
602
|
+
|
603
|
+
message_id = next_msgid
|
604
|
+
|
605
|
+
write(request, nil, message_id)
|
606
|
+
pdu = queued_read(message_id)
|
607
|
+
|
608
|
+
if !pdu || pdu.app_tag != Net::LDAP::PDU::ExtendedResponse
|
609
|
+
raise Net::LDAP::ResponseMissingError, "response missing or invalid"
|
610
|
+
end
|
611
|
+
|
612
|
+
pdu
|
613
|
+
end
|
614
|
+
|
634
615
|
#--
|
635
616
|
# TODO: need to support a time limit, in case the server fails to respond.
|
636
617
|
# Unlike other operation-methods in this class, we return a result hash
|
@@ -641,9 +622,9 @@ class Net::LDAP::Connection #:nodoc:
|
|
641
622
|
def add(args)
|
642
623
|
add_dn = args[:dn] or raise Net::LDAP::EmptyDNError, "Unable to add empty DN"
|
643
624
|
add_attrs = []
|
644
|
-
a = args[:attributes] and a.each
|
645
|
-
add_attrs << [
|
646
|
-
|
625
|
+
a = args[:attributes] and a.each do |k, v|
|
626
|
+
add_attrs << [k.to_s.to_ber, Array(v).map(&:to_ber).to_ber_set].to_ber_sequence
|
627
|
+
end
|
647
628
|
|
648
629
|
message_id = next_msgid
|
649
630
|
request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(Net::LDAP::PDU::AddRequest)
|
@@ -652,7 +633,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
652
633
|
pdu = queued_read(message_id)
|
653
634
|
|
654
635
|
if !pdu || pdu.app_tag != Net::LDAP::PDU::AddResponse
|
655
|
-
raise Net::LDAP::
|
636
|
+
raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid"
|
656
637
|
end
|
657
638
|
|
658
639
|
pdu
|
@@ -699,4 +680,33 @@ class Net::LDAP::Connection #:nodoc:
|
|
699
680
|
|
700
681
|
pdu
|
701
682
|
end
|
683
|
+
|
684
|
+
# Internal: Returns a Socket like object used internally to communicate with
|
685
|
+
# LDAP server.
|
686
|
+
#
|
687
|
+
# Typically a TCPSocket, but can be a OpenSSL::SSL::SSLSocket
|
688
|
+
def socket
|
689
|
+
return @conn if defined? @conn
|
690
|
+
|
691
|
+
# First refactoring uses the existing methods open_connection and
|
692
|
+
# prepare_socket to set @conn. Next cleanup would centralize connection
|
693
|
+
# handling here.
|
694
|
+
if @server[:socket]
|
695
|
+
prepare_socket(@server)
|
696
|
+
else
|
697
|
+
@server[:hosts] = [[@server[:host], @server[:port]]] if @server[:hosts].nil?
|
698
|
+
open_connection(@server)
|
699
|
+
end
|
700
|
+
|
701
|
+
@conn
|
702
|
+
end
|
703
|
+
|
704
|
+
private
|
705
|
+
|
706
|
+
# Wrap around Socket.tcp to normalize with other Socket initializers
|
707
|
+
class DefaultSocket
|
708
|
+
def self.new(host, port, socket_opts = {})
|
709
|
+
Socket.tcp(host, port, socket_opts)
|
710
|
+
end
|
711
|
+
end
|
702
712
|
end # class Connection
|
data/lib/net/ldap/dataset.rb
CHANGED
@@ -29,7 +29,7 @@ class Net::LDAP::Dataset < Hash
|
|
29
29
|
keys.sort.each do |dn|
|
30
30
|
ary << "dn: #{dn}"
|
31
31
|
|
32
|
-
attributes = self[dn].keys.map
|
32
|
+
attributes = self[dn].keys.map(&:to_s).sort
|
33
33
|
attributes.each do |attr|
|
34
34
|
self[dn][attr.to_sym].each do |value|
|
35
35
|
if attr == "userpassword" or value_is_binary?(value)
|
@@ -141,7 +141,7 @@ class Net::LDAP::Dataset < Hash
|
|
141
141
|
# $' is the dn-value
|
142
142
|
# Avoid the Base64 class because not all Ruby versions have it.
|
143
143
|
dn = ($1 == ":") ? $'.unpack('m').shift : $'
|
144
|
-
ds[dn] = Hash.new { |k,v| k[v] = [] }
|
144
|
+
ds[dn] = Hash.new { |k, v| k[v] = [] }
|
145
145
|
yield :dn, dn if block_given?
|
146
146
|
elsif line.empty?
|
147
147
|
dn = nil
|
data/lib/net/ldap/dn.rb
CHANGED
@@ -169,11 +169,10 @@ class Net::LDAP::DN
|
|
169
169
|
end
|
170
170
|
|
171
171
|
# Last pair
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
end
|
172
|
+
raise "DN badly formed" unless
|
173
|
+
[:value, :value_normal, :value_hexstring, :value_end].include? state
|
174
|
+
|
175
|
+
yield key.string.strip, value.string.rstrip
|
177
176
|
end
|
178
177
|
|
179
178
|
##
|
data/lib/net/ldap/entry.rb
CHANGED
@@ -140,11 +140,10 @@ class Net::LDAP::Entry
|
|
140
140
|
# arguments to the block: a Symbol giving the name of the attribute, and a
|
141
141
|
# (possibly empty) \Array of data values.
|
142
142
|
def each # :yields: attribute-name, data-values-array
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
}
|
143
|
+
return unless block_given?
|
144
|
+
attribute_names.each do|a|
|
145
|
+
attr_name, values = a, self[a]
|
146
|
+
yield attr_name, values
|
148
147
|
end
|
149
148
|
end
|
150
149
|
alias_method :each_attribute, :each
|
data/lib/net/ldap/error.rb
CHANGED
@@ -9,7 +9,42 @@ class Net::LDAP
|
|
9
9
|
|
10
10
|
class AlreadyOpenedError < Error; end
|
11
11
|
class SocketError < Error; end
|
12
|
-
class ConnectionRefusedError < Error;
|
12
|
+
class ConnectionRefusedError < Error;
|
13
|
+
def initialize(*args)
|
14
|
+
warn_deprecation_message
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
warn_deprecation_message
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def warn_deprecation_message
|
26
|
+
warn "Deprecation warning: Net::LDAP::ConnectionRefused will be deprecated. Use Errno::ECONNREFUSED instead."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
class ConnectionError < Error
|
30
|
+
def self.new(errors)
|
31
|
+
error = errors.first.first
|
32
|
+
if errors.size == 1
|
33
|
+
if error.kind_of? Errno::ECONNREFUSED
|
34
|
+
return Net::LDAP::ConnectionRefusedError.new(error.message)
|
35
|
+
end
|
36
|
+
|
37
|
+
return Net::LDAP::Error.new(error.message)
|
38
|
+
end
|
39
|
+
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(errors)
|
44
|
+
message = "Unable to connect to any given server: \n #{errors.map { |e, h, p| "#{e.class}: #{e.message} (#{h}:#{p})" }.join("\n ")}"
|
45
|
+
super(message)
|
46
|
+
end
|
47
|
+
end
|
13
48
|
class NoOpenSSLError < Error; end
|
14
49
|
class NoStartTLSResultError < Error; end
|
15
50
|
class NoSearchBaseError < Error; end
|
data/lib/net/ldap/filter.rb
CHANGED
@@ -23,7 +23,7 @@
|
|
23
23
|
class Net::LDAP::Filter
|
24
24
|
##
|
25
25
|
# Known filter types.
|
26
|
-
FilterTypes = [
|
26
|
+
FilterTypes = [:ne, :eq, :ge, :le, :and, :or, :not, :ex, :bineq]
|
27
27
|
|
28
28
|
def initialize(op, left, right) #:nodoc:
|
29
29
|
unless FilterTypes.include?(op)
|
@@ -287,7 +287,7 @@ class Net::LDAP::Filter
|
|
287
287
|
when 0xa4 # context-specific constructed 4, "substring"
|
288
288
|
str = ""
|
289
289
|
final = false
|
290
|
-
ber.last.each
|
290
|
+
ber.last.each do |b|
|
291
291
|
case b.ber_identifier
|
292
292
|
when 0x80 # context-specific primitive 0, SubstringFilter "initial"
|
293
293
|
raise Net::LDAP::SubstringFilterError, "Unrecognized substring filter; bad initial value." if str.length > 0
|
@@ -298,7 +298,7 @@ class Net::LDAP::Filter
|
|
298
298
|
str += "*#{escape(b)}"
|
299
299
|
final = true
|
300
300
|
end
|
301
|
-
|
301
|
+
end
|
302
302
|
str += "*" unless final
|
303
303
|
eq(ber.first.to_s, str)
|
304
304
|
when 0xa5 # context-specific constructed 5, "greaterOrEqual"
|
@@ -550,10 +550,10 @@ class Net::LDAP::Filter
|
|
550
550
|
[self.class.eq(@left, @right).to_ber].to_ber_contextspecific(2)
|
551
551
|
when :and
|
552
552
|
ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
|
553
|
-
ary.map
|
553
|
+
ary.map(&:to_ber).to_ber_contextspecific(0)
|
554
554
|
when :or
|
555
555
|
ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten
|
556
|
-
ary.map
|
556
|
+
ary.map(&:to_ber).to_ber_contextspecific(1)
|
557
557
|
when :not
|
558
558
|
[@left.to_ber].to_ber_contextspecific(2)
|
559
559
|
end
|
@@ -752,7 +752,7 @@ class Net::LDAP::Filter
|
|
752
752
|
scanner.scan(/\s*/)
|
753
753
|
if op = scanner.scan(/<=|>=|!=|:=|=/)
|
754
754
|
scanner.scan(/\s*/)
|
755
|
-
if value = scanner.scan(/(?:[-\[\]{}\w
|
755
|
+
if value = scanner.scan(/(?:[-\[\]{}\w*.+\/:@=,#\$%&!'^~\s\xC3\x80-\xCA\xAF]|[^\x00-\x7F]|\\[a-fA-F\d]{2})+/u)
|
756
756
|
# 20100313 AZ: Assumes that "(uid=george*)" is the same as
|
757
757
|
# "(uid=george* )". The standard doesn't specify, but I can find
|
758
758
|
# no examples that suggest otherwise.
|