net-ldap 0.11 → 0.16.0
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.
- 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.
|