net-ldap 0.12.0 → 0.17.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.
- checksums.yaml +5 -5
- data/Contributors.rdoc +1 -0
- data/History.rdoc +89 -0
- data/README.rdoc +19 -9
- 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/core_ext.rb +6 -6
- data/lib/net/ber.rb +39 -9
- data/lib/net/ldap/auth_adapter/gss_spnego.rb +9 -8
- data/lib/net/ldap/auth_adapter/sasl.rb +6 -4
- data/lib/net/ldap/auth_adapter/simple.rb +1 -1
- data/lib/net/ldap/connection.rb +163 -50
- data/lib/net/ldap/dataset.rb +5 -5
- data/lib/net/ldap/dn.rb +13 -14
- data/lib/net/ldap/entry.rb +17 -7
- data/lib/net/ldap/error.rb +2 -25
- data/lib/net/ldap/filter.rb +15 -8
- data/lib/net/ldap/instrumentation.rb +2 -2
- data/lib/net/ldap/password.rb +7 -5
- data/lib/net/ldap/pdu.rb +27 -3
- data/lib/net/ldap/version.rb +1 -1
- data/lib/net/ldap.rb +209 -90
- data/lib/net/snmp.rb +19 -19
- data/lib/net-ldap.rb +1 -1
- metadata +30 -99
- data/.gitignore +0 -9
- data/.rubocop.yml +0 -5
- data/.rubocop_todo.yml +0 -462
- data/.travis.yml +0 -31
- data/CONTRIBUTING.md +0 -54
- data/Gemfile +0 -2
- data/Rakefile +0 -23
- data/net-ldap.gemspec +0 -36
- data/script/changelog +0 -47
- data/script/install-openldap +0 -112
- data/script/package +0 -7
- data/script/release +0 -16
- data/test/ber/core_ext/test_array.rb +0 -22
- data/test/ber/core_ext/test_string.rb +0 -25
- data/test/ber/test_ber.rb +0 -145
- data/test/fixtures/cacert.pem +0 -20
- data/test/fixtures/openldap/memberof.ldif +0 -33
- data/test/fixtures/openldap/retcode.ldif +0 -76
- data/test/fixtures/openldap/slapd.conf.ldif +0 -67
- data/test/fixtures/seed.ldif +0 -374
- data/test/integration/test_add.rb +0 -28
- data/test/integration/test_ber.rb +0 -30
- data/test/integration/test_bind.rb +0 -34
- data/test/integration/test_delete.rb +0 -31
- data/test/integration/test_open.rb +0 -88
- data/test/integration/test_return_codes.rb +0 -38
- data/test/integration/test_search.rb +0 -77
- data/test/support/vm/openldap/.gitignore +0 -1
- data/test/support/vm/openldap/README.md +0 -32
- data/test/support/vm/openldap/Vagrantfile +0 -33
- data/test/test_auth_adapter.rb +0 -11
- data/test/test_dn.rb +0 -44
- data/test/test_entry.rb +0 -65
- data/test/test_filter.rb +0 -223
- data/test/test_filter_parser.rb +0 -24
- data/test/test_helper.rb +0 -66
- data/test/test_ldap.rb +0 -67
- data/test/test_ldap_connection.rb +0 -460
- data/test/test_ldif.rb +0 -104
- data/test/test_password.rb +0 -10
- data/test/test_rename.rb +0 -77
- data/test/test_search.rb +0 -39
- data/test/test_snmp.rb +0 -119
- data/test/test_ssl_ber.rb +0 -40
- data/test/testdata.ldif +0 -101
- data/testserver/ldapserver.rb +0 -210
- data/testserver/testdata.ldif +0 -101
data/lib/net/ldap/connection.rb
CHANGED
@@ -3,38 +3,63 @@
|
|
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
|
-
else
|
15
|
-
server[:hosts] = [[server[:host], server[:port]]] if server[:hosts].nil?
|
16
|
-
open_connection(server)
|
17
|
-
end
|
23
|
+
# Allows tests to parameterize what socket class to use
|
24
|
+
@socket_class = server.fetch(:socket_class, DefaultSocket)
|
18
25
|
|
19
26
|
yield self if block_given?
|
20
27
|
end
|
21
28
|
|
22
|
-
def
|
29
|
+
def socket_class=(socket_class)
|
30
|
+
@socket_class = socket_class
|
31
|
+
end
|
32
|
+
|
33
|
+
def prepare_socket(server, timeout=nil)
|
23
34
|
socket = server[:socket]
|
24
35
|
encryption = server[:encryption]
|
25
36
|
|
26
37
|
@conn = socket
|
27
|
-
setup_encryption
|
38
|
+
setup_encryption(encryption, timeout) if encryption
|
28
39
|
end
|
29
40
|
|
30
41
|
def open_connection(server)
|
31
42
|
hosts = server[:hosts]
|
32
43
|
encryption = server[:encryption]
|
33
44
|
|
45
|
+
timeout = server[:connect_timeout] || DefaultConnectTimeout
|
46
|
+
socket_opts = {
|
47
|
+
connect_timeout: timeout,
|
48
|
+
}
|
49
|
+
|
34
50
|
errors = []
|
35
51
|
hosts.each do |host, port|
|
36
52
|
begin
|
37
|
-
prepare_socket(server.merge(socket:
|
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
|
38
63
|
return
|
39
64
|
rescue Net::LDAP::Error, SocketError, SystemCallError,
|
40
65
|
OpenSSL::SSL::SSLError => e
|
@@ -60,7 +85,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
60
85
|
end
|
61
86
|
end
|
62
87
|
|
63
|
-
def self.wrap_with_ssl(io, tls_options = {})
|
88
|
+
def self.wrap_with_ssl(io, tls_options = {}, timeout=nil)
|
64
89
|
raise Net::LDAP::NoOpenSSLError, "OpenSSL is unavailable" unless Net::LDAP::HasOpenSSL
|
65
90
|
|
66
91
|
ctx = OpenSSL::SSL::SSLContext.new
|
@@ -70,7 +95,22 @@ class Net::LDAP::Connection #:nodoc:
|
|
70
95
|
ctx.set_params(tls_options) unless tls_options.empty?
|
71
96
|
|
72
97
|
conn = OpenSSL::SSL::SSLSocket.new(io, ctx)
|
73
|
-
|
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
|
74
114
|
|
75
115
|
# Doesn't work:
|
76
116
|
# conn.sync_close = true
|
@@ -107,17 +147,17 @@ class Net::LDAP::Connection #:nodoc:
|
|
107
147
|
# communications, as with simple_tls. Thanks for Kouhei Sutou for
|
108
148
|
# generously contributing the :start_tls path.
|
109
149
|
#++
|
110
|
-
def setup_encryption(args)
|
150
|
+
def setup_encryption(args, timeout=nil)
|
111
151
|
args[:tls_options] ||= {}
|
112
152
|
case args[:method]
|
113
153
|
when :simple_tls
|
114
|
-
@conn = self.class.wrap_with_ssl(@conn, args[:tls_options])
|
154
|
+
@conn = self.class.wrap_with_ssl(@conn, args[:tls_options], timeout)
|
115
155
|
# additional branches requiring server validation and peer certs, etc.
|
116
156
|
# go here.
|
117
157
|
when :start_tls
|
118
158
|
message_id = next_msgid
|
119
159
|
request = [
|
120
|
-
Net::LDAP::StartTlsOid.to_ber_contextspecific(0)
|
160
|
+
Net::LDAP::StartTlsOid.to_ber_contextspecific(0),
|
121
161
|
].to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
|
122
162
|
|
123
163
|
write(request, nil, message_id)
|
@@ -127,11 +167,9 @@ class Net::LDAP::Connection #:nodoc:
|
|
127
167
|
raise Net::LDAP::NoStartTLSResultError, "no start_tls result"
|
128
168
|
end
|
129
169
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
raise Net::LDAP::StartTlSError, "start_tls failed: #{pdu.result_code}"
|
134
|
-
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)
|
135
173
|
else
|
136
174
|
raise Net::LDAP::EncMethodUnsupportedError, "unsupported encryption method #{args[:method]}"
|
137
175
|
end
|
@@ -143,7 +181,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
143
181
|
# have to call it, but perhaps it will come in handy someday.
|
144
182
|
#++
|
145
183
|
def close
|
146
|
-
return if @conn.nil?
|
184
|
+
return if !defined?(@conn) || @conn.nil?
|
147
185
|
@conn.close
|
148
186
|
@conn = nil
|
149
187
|
end
|
@@ -161,12 +199,10 @@ class Net::LDAP::Connection #:nodoc:
|
|
161
199
|
|
162
200
|
# read messages until we have a match for the given message_id
|
163
201
|
while pdu = read
|
164
|
-
if pdu.message_id == message_id
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
next
|
169
|
-
end
|
202
|
+
return pdu if pdu.message_id == message_id
|
203
|
+
|
204
|
+
message_queue[pdu.message_id].push pdu
|
205
|
+
next
|
170
206
|
end
|
171
207
|
|
172
208
|
pdu
|
@@ -195,7 +231,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
195
231
|
def read(syntax = Net::LDAP::AsnSyntax)
|
196
232
|
ber_object =
|
197
233
|
instrument "read.net_ldap_connection", :syntax => syntax do |payload|
|
198
|
-
|
234
|
+
socket.read_ber(syntax) do |id, content_length|
|
199
235
|
payload[:object_type_id] = id
|
200
236
|
payload[:content_length] = content_length
|
201
237
|
end
|
@@ -225,7 +261,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
225
261
|
def write(request, controls = nil, message_id = next_msgid)
|
226
262
|
instrument "write.net_ldap_connection" do |payload|
|
227
263
|
packet = [message_id.to_ber, request, controls].compact.to_ber_sequence
|
228
|
-
payload[:content_length] =
|
264
|
+
payload[:content_length] = socket.write(packet)
|
229
265
|
end
|
230
266
|
end
|
231
267
|
private :write
|
@@ -264,10 +300,10 @@ class Net::LDAP::Connection #:nodoc:
|
|
264
300
|
control[2] = (control[2] == true).to_ber
|
265
301
|
control.to_ber_sequence
|
266
302
|
end
|
267
|
-
|
303
|
+
[
|
268
304
|
Net::LDAP::LDAPControls::SORT_REQUEST.to_ber,
|
269
305
|
false.to_ber,
|
270
|
-
sort_control_values.to_ber_sequence.to_s.to_ber
|
306
|
+
sort_control_values.to_ber_sequence.to_s.to_ber,
|
271
307
|
].to_ber_sequence
|
272
308
|
end
|
273
309
|
|
@@ -364,12 +400,11 @@ class Net::LDAP::Connection #:nodoc:
|
|
364
400
|
# should collect this into a private helper to clarify the structure
|
365
401
|
query_limit = 0
|
366
402
|
if size > 0
|
367
|
-
if paged
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
end
|
403
|
+
query_limit = if paged
|
404
|
+
(((size - n_results) < 126) ? (size - n_results) : 0)
|
405
|
+
else
|
406
|
+
size
|
407
|
+
end
|
373
408
|
end
|
374
409
|
|
375
410
|
request = [
|
@@ -380,7 +415,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
380
415
|
time.to_ber,
|
381
416
|
attrs_only.to_ber,
|
382
417
|
filter.to_ber,
|
383
|
-
ber_attrs.to_ber_sequence
|
418
|
+
ber_attrs.to_ber_sequence,
|
384
419
|
].to_ber_appsequence(Net::LDAP::PDU::SearchRequest)
|
385
420
|
|
386
421
|
# rfc2696_cookie sometimes contains binary data from Microsoft Active Directory
|
@@ -393,7 +428,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
393
428
|
Net::LDAP::LDAPControls::PAGED_RESULTS.to_ber,
|
394
429
|
# Criticality MUST be false to interoperate with normal LDAPs.
|
395
430
|
false.to_ber,
|
396
|
-
rfc2696_cookie.map
|
431
|
+
rfc2696_cookie.map(&:to_ber).to_ber_sequence.to_s.to_ber,
|
397
432
|
].to_ber_sequence if paged
|
398
433
|
controls << ber_sort if ber_sort
|
399
434
|
controls = controls.empty? ? nil : controls.to_ber_contextspecific(0)
|
@@ -432,6 +467,10 @@ class Net::LDAP::Connection #:nodoc:
|
|
432
467
|
end
|
433
468
|
end
|
434
469
|
|
470
|
+
if result_pdu.nil?
|
471
|
+
raise Net::LDAP::ResponseMissingOrInvalidError, "response missing"
|
472
|
+
end
|
473
|
+
|
435
474
|
# count number of pages of results
|
436
475
|
payload[:page_count] ||= 0
|
437
476
|
payload[:page_count] += 1
|
@@ -487,20 +526,20 @@ class Net::LDAP::Connection #:nodoc:
|
|
487
526
|
MODIFY_OPERATIONS = { #:nodoc:
|
488
527
|
:add => 0,
|
489
528
|
:delete => 1,
|
490
|
-
:replace => 2
|
529
|
+
:replace => 2,
|
491
530
|
}
|
492
531
|
|
493
532
|
def self.modify_ops(operations)
|
494
533
|
ops = []
|
495
534
|
if operations
|
496
|
-
operations.each
|
535
|
+
operations.each do |op, attrib, values|
|
497
536
|
# TODO, fix the following line, which gives a bogus error if the
|
498
537
|
# opcode is invalid.
|
499
538
|
op_ber = MODIFY_OPERATIONS[op.to_sym].to_ber_enumerated
|
500
|
-
values = [
|
501
|
-
values = [
|
502
|
-
ops << [
|
503
|
-
|
539
|
+
values = [values].flatten.map { |v| v.to_ber if v }.to_ber_set
|
540
|
+
values = [attrib.to_s.to_ber, values].to_ber_sequence
|
541
|
+
ops << [op_ber, values].to_ber
|
542
|
+
end
|
504
543
|
end
|
505
544
|
ops
|
506
545
|
end
|
@@ -519,7 +558,7 @@ class Net::LDAP::Connection #:nodoc:
|
|
519
558
|
message_id = next_msgid
|
520
559
|
request = [
|
521
560
|
modify_dn.to_ber,
|
522
|
-
ops.to_ber_sequence
|
561
|
+
ops.to_ber_sequence,
|
523
562
|
].to_ber_appsequence(Net::LDAP::PDU::ModifyRequest)
|
524
563
|
|
525
564
|
write(request, nil, message_id)
|
@@ -532,6 +571,51 @@ class Net::LDAP::Connection #:nodoc:
|
|
532
571
|
pdu
|
533
572
|
end
|
534
573
|
|
574
|
+
##
|
575
|
+
# Password Modify
|
576
|
+
#
|
577
|
+
# http://tools.ietf.org/html/rfc3062
|
578
|
+
#
|
579
|
+
# passwdModifyOID OBJECT IDENTIFIER ::= 1.3.6.1.4.1.4203.1.11.1
|
580
|
+
#
|
581
|
+
# PasswdModifyRequestValue ::= SEQUENCE {
|
582
|
+
# userIdentity [0] OCTET STRING OPTIONAL
|
583
|
+
# oldPasswd [1] OCTET STRING OPTIONAL
|
584
|
+
# newPasswd [2] OCTET STRING OPTIONAL }
|
585
|
+
#
|
586
|
+
# PasswdModifyResponseValue ::= SEQUENCE {
|
587
|
+
# genPasswd [0] OCTET STRING OPTIONAL }
|
588
|
+
#
|
589
|
+
# Encoded request:
|
590
|
+
#
|
591
|
+
# 00\x02\x01\x02w+\x80\x171.3.6.1.4.1.4203.1.11.1\x81\x100\x0E\x81\x05old\x82\x05new
|
592
|
+
#
|
593
|
+
def password_modify(args)
|
594
|
+
dn = args[:dn]
|
595
|
+
raise ArgumentError, 'DN is required' if !dn || dn.empty?
|
596
|
+
|
597
|
+
ext_seq = [Net::LDAP::PasswdModifyOid.to_ber_contextspecific(0)]
|
598
|
+
|
599
|
+
pwd_seq = []
|
600
|
+
pwd_seq << dn.to_ber(0x80)
|
601
|
+
pwd_seq << args[:old_password].to_ber(0x81) unless args[:old_password].nil?
|
602
|
+
pwd_seq << args[:new_password].to_ber(0x82) unless args[:new_password].nil?
|
603
|
+
ext_seq << pwd_seq.to_ber_sequence.to_ber(0x81)
|
604
|
+
|
605
|
+
request = ext_seq.to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
|
606
|
+
|
607
|
+
message_id = next_msgid
|
608
|
+
|
609
|
+
write(request, nil, message_id)
|
610
|
+
pdu = queued_read(message_id)
|
611
|
+
|
612
|
+
if !pdu || pdu.app_tag != Net::LDAP::PDU::ExtendedResponse
|
613
|
+
raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid"
|
614
|
+
end
|
615
|
+
|
616
|
+
pdu
|
617
|
+
end
|
618
|
+
|
535
619
|
#--
|
536
620
|
# TODO: need to support a time limit, in case the server fails to respond.
|
537
621
|
# Unlike other operation-methods in this class, we return a result hash
|
@@ -542,9 +626,9 @@ class Net::LDAP::Connection #:nodoc:
|
|
542
626
|
def add(args)
|
543
627
|
add_dn = args[:dn] or raise Net::LDAP::EmptyDNError, "Unable to add empty DN"
|
544
628
|
add_attrs = []
|
545
|
-
a = args[:attributes] and a.each
|
546
|
-
add_attrs << [
|
547
|
-
|
629
|
+
a = args[:attributes] and a.each do |k, v|
|
630
|
+
add_attrs << [k.to_s.to_ber, Array(v).map(&:to_ber).to_ber_set].to_ber_sequence
|
631
|
+
end
|
548
632
|
|
549
633
|
message_id = next_msgid
|
550
634
|
request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(Net::LDAP::PDU::AddRequest)
|
@@ -600,4 +684,33 @@ class Net::LDAP::Connection #:nodoc:
|
|
600
684
|
|
601
685
|
pdu
|
602
686
|
end
|
687
|
+
|
688
|
+
# Internal: Returns a Socket like object used internally to communicate with
|
689
|
+
# LDAP server.
|
690
|
+
#
|
691
|
+
# Typically a TCPSocket, but can be a OpenSSL::SSL::SSLSocket
|
692
|
+
def socket
|
693
|
+
return @conn if defined?(@conn) && !@conn.nil?
|
694
|
+
|
695
|
+
# First refactoring uses the existing methods open_connection and
|
696
|
+
# prepare_socket to set @conn. Next cleanup would centralize connection
|
697
|
+
# handling here.
|
698
|
+
if @server[:socket]
|
699
|
+
prepare_socket(@server)
|
700
|
+
else
|
701
|
+
@server[:hosts] = [[@server[:host], @server[:port]]] if @server[:hosts].nil?
|
702
|
+
open_connection(@server)
|
703
|
+
end
|
704
|
+
|
705
|
+
@conn
|
706
|
+
end
|
707
|
+
|
708
|
+
private
|
709
|
+
|
710
|
+
# Wrap around Socket.tcp to normalize with other Socket initializers
|
711
|
+
class DefaultSocket
|
712
|
+
def self.new(host, port, socket_opts = {})
|
713
|
+
Socket.tcp(host, port, **socket_opts)
|
714
|
+
end
|
715
|
+
end
|
603
716
|
end # class Connection
|
data/lib/net/ldap/dataset.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative 'entry'
|
2
|
+
|
1
3
|
# -*- ruby encoding: utf-8 -*-
|
2
4
|
##
|
3
5
|
# An LDAP Dataset. Used primarily as an intermediate format for converting
|
@@ -29,7 +31,7 @@ class Net::LDAP::Dataset < Hash
|
|
29
31
|
keys.sort.each do |dn|
|
30
32
|
ary << "dn: #{dn}"
|
31
33
|
|
32
|
-
attributes = self[dn].keys.map
|
34
|
+
attributes = self[dn].keys.map(&:to_s).sort
|
33
35
|
attributes.each do |attr|
|
34
36
|
self[dn][attr.to_sym].each do |value|
|
35
37
|
if attr == "userpassword" or value_is_binary?(value)
|
@@ -103,7 +105,7 @@ class Net::LDAP::Dataset < Hash
|
|
103
105
|
# with the conversion of
|
104
106
|
def from_entry(entry)
|
105
107
|
dataset = Net::LDAP::Dataset.new
|
106
|
-
hash = {
|
108
|
+
hash = {}
|
107
109
|
entry.each_attribute do |attribute, value|
|
108
110
|
next if attribute == :dn
|
109
111
|
hash[attribute] = value
|
@@ -141,7 +143,7 @@ class Net::LDAP::Dataset < Hash
|
|
141
143
|
# $' is the dn-value
|
142
144
|
# Avoid the Base64 class because not all Ruby versions have it.
|
143
145
|
dn = ($1 == ":") ? $'.unpack('m').shift : $'
|
144
|
-
ds[dn] = Hash.new { |k,v| k[v] = [] }
|
146
|
+
ds[dn] = Hash.new { |k, v| k[v] = [] }
|
145
147
|
yield :dn, dn if block_given?
|
146
148
|
elsif line.empty?
|
147
149
|
dn = nil
|
@@ -164,5 +166,3 @@ class Net::LDAP::Dataset < Hash
|
|
164
166
|
end
|
165
167
|
end
|
166
168
|
end
|
167
|
-
|
168
|
-
require 'net/ldap/entry' unless defined? Net::LDAP::Entry
|
data/lib/net/ldap/dn.rb
CHANGED
@@ -57,19 +57,19 @@ class Net::LDAP::DN
|
|
57
57
|
state = :key_oid
|
58
58
|
key << char
|
59
59
|
when ' ' then state = :key
|
60
|
-
else raise "DN badly formed"
|
60
|
+
else raise Net::LDAP::InvalidDNError, "DN badly formed"
|
61
61
|
end
|
62
62
|
when :key_normal then
|
63
63
|
case char
|
64
64
|
when '=' then state = :value
|
65
65
|
when 'a'..'z', 'A'..'Z', '0'..'9', '-', ' ' then key << char
|
66
|
-
else raise "DN badly formed"
|
66
|
+
else raise Net::LDAP::InvalidDNError, "DN badly formed"
|
67
67
|
end
|
68
68
|
when :key_oid then
|
69
69
|
case char
|
70
70
|
when '=' then state = :value
|
71
71
|
when '0'..'9', '.', ' ' then key << char
|
72
|
-
else raise "DN badly formed"
|
72
|
+
else raise Net::LDAP::InvalidDNError, "DN badly formed"
|
73
73
|
end
|
74
74
|
when :value then
|
75
75
|
case char
|
@@ -110,7 +110,7 @@ class Net::LDAP::DN
|
|
110
110
|
when '0'..'9', 'a'..'f', 'A'..'F' then
|
111
111
|
state = :value_normal
|
112
112
|
value << "#{hex_buffer}#{char}".to_i(16).chr
|
113
|
-
else raise "DN badly formed"
|
113
|
+
else raise Net::LDAP::InvalidDNError, "DN badly formed"
|
114
114
|
end
|
115
115
|
when :value_quoted then
|
116
116
|
case char
|
@@ -132,7 +132,7 @@ class Net::LDAP::DN
|
|
132
132
|
when '0'..'9', 'a'..'f', 'A'..'F' then
|
133
133
|
state = :value_quoted
|
134
134
|
value << "#{hex_buffer}#{char}".to_i(16).chr
|
135
|
-
else raise "DN badly formed"
|
135
|
+
else raise Net::LDAP::InvalidDNError, "DN badly formed"
|
136
136
|
end
|
137
137
|
when :value_hexstring then
|
138
138
|
case char
|
@@ -145,14 +145,14 @@ class Net::LDAP::DN
|
|
145
145
|
yield key.string.strip, value.string.rstrip
|
146
146
|
key = StringIO.new
|
147
147
|
value = StringIO.new;
|
148
|
-
else raise "DN badly formed"
|
148
|
+
else raise Net::LDAP::InvalidDNError, "DN badly formed"
|
149
149
|
end
|
150
150
|
when :value_hexstring_hex then
|
151
151
|
case char
|
152
152
|
when '0'..'9', 'a'..'f', 'A'..'F' then
|
153
153
|
state = :value_hexstring
|
154
154
|
value << char
|
155
|
-
else raise "DN badly formed"
|
155
|
+
else raise Net::LDAP::InvalidDNError, "DN badly formed"
|
156
156
|
end
|
157
157
|
when :value_end then
|
158
158
|
case char
|
@@ -162,18 +162,17 @@ class Net::LDAP::DN
|
|
162
162
|
yield key.string.strip, value.string.rstrip
|
163
163
|
key = StringIO.new
|
164
164
|
value = StringIO.new;
|
165
|
-
else raise "DN badly formed"
|
165
|
+
else raise Net::LDAP::InvalidDNError, "DN badly formed"
|
166
166
|
end
|
167
|
-
else raise "Fell out of state machine"
|
167
|
+
else raise Net::LDAP::InvalidDNError, "Fell out of state machine"
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
171
171
|
# Last pair
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
end
|
172
|
+
raise Net::LDAP::InvalidDNError, "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
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative 'dataset'
|
2
|
+
|
1
3
|
# -*- ruby encoding: utf-8 -*-
|
2
4
|
##
|
3
5
|
# Objects of this class represent individual entries in an LDAP directory.
|
@@ -133,6 +135,13 @@ class Net::LDAP::Entry
|
|
133
135
|
@myhash.keys
|
134
136
|
end
|
135
137
|
|
138
|
+
##
|
139
|
+
# Creates a duplicate of the internal Hash containing the attributes
|
140
|
+
# of the entry.
|
141
|
+
def to_h
|
142
|
+
@myhash.dup
|
143
|
+
end
|
144
|
+
|
136
145
|
##
|
137
146
|
# Accesses each of the attributes present in the Entry.
|
138
147
|
#
|
@@ -140,11 +149,10 @@ class Net::LDAP::Entry
|
|
140
149
|
# arguments to the block: a Symbol giving the name of the attribute, and a
|
141
150
|
# (possibly empty) \Array of data values.
|
142
151
|
def each # :yields: attribute-name, data-values-array
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
}
|
152
|
+
return unless block_given?
|
153
|
+
attribute_names.each do|a|
|
154
|
+
attr_name, values = a, self[a]
|
155
|
+
yield attr_name, values
|
148
156
|
end
|
149
157
|
end
|
150
158
|
alias_method :each_attribute, :each
|
@@ -188,6 +196,8 @@ class Net::LDAP::Entry
|
|
188
196
|
sym.to_s[-1] == ?=
|
189
197
|
end
|
190
198
|
private :setter?
|
191
|
-
end # class Entry
|
192
199
|
|
193
|
-
|
200
|
+
def ==(other)
|
201
|
+
other.instance_of?(self.class) && @myhash == other.to_h
|
202
|
+
end
|
203
|
+
end # class Entry
|
data/lib/net/ldap/error.rb
CHANGED
@@ -1,37 +1,13 @@
|
|
1
1
|
class Net::LDAP
|
2
|
-
class LdapError < StandardError
|
3
|
-
def message
|
4
|
-
"Deprecation warning: Net::LDAP::LdapError is no longer used. Use Net::LDAP::Error or rescue one of it's subclasses. \n" + super
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
2
|
class Error < StandardError; end
|
9
3
|
|
10
4
|
class AlreadyOpenedError < Error; end
|
11
5
|
class SocketError < Error; end
|
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
|
-
def warn_deprecation_message
|
25
|
-
warn "Deprecation warning: Net::LDAP::ConnectionRefused will be deprecated. Use Errno::ECONNREFUSED instead."
|
26
|
-
end
|
27
|
-
end
|
28
6
|
class ConnectionError < Error
|
29
7
|
def self.new(errors)
|
30
8
|
error = errors.first.first
|
31
9
|
if errors.size == 1
|
32
|
-
if error.
|
33
|
-
return Net::LDAP::ConnectionRefusedError.new(error.message)
|
34
|
-
end
|
10
|
+
return error if error.is_a? Errno::ECONNREFUSED
|
35
11
|
|
36
12
|
return Net::LDAP::Error.new(error.message)
|
37
13
|
end
|
@@ -59,6 +35,7 @@ class Net::LDAP
|
|
59
35
|
class ResponseTypeInvalidError < Error; end
|
60
36
|
class ResponseMissingOrInvalidError < Error; end
|
61
37
|
class EmptyDNError < Error; end
|
38
|
+
class InvalidDNError < Error; end
|
62
39
|
class HashTypeUnsupportedError < Error; end
|
63
40
|
class OperatorError < Error; end
|
64
41
|
class SubstringFilterError < 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"
|
@@ -490,7 +490,7 @@ class Net::LDAP::Filter
|
|
490
490
|
when :eq
|
491
491
|
if @right == "*" # presence test
|
492
492
|
@left.to_s.to_ber_contextspecific(7)
|
493
|
-
elsif @right =~ /[*]/ # substring
|
493
|
+
elsif @right.to_s =~ /[*]/ # substring
|
494
494
|
# Parsing substrings is a little tricky. We use String#split to
|
495
495
|
# break a string into substrings delimited by the * (star)
|
496
496
|
# character. But we also need to know whether there is a star at the
|
@@ -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
|
@@ -645,8 +645,15 @@ class Net::LDAP::Filter
|
|
645
645
|
|
646
646
|
##
|
647
647
|
# Converts escaped characters (e.g., "\\28") to unescaped characters
|
648
|
+
# @note slawson20170317: Don't attempt to unescape 16 byte binary data which we assume are objectGUIDs
|
649
|
+
# The binary form of 5936AE79-664F-44EA-BCCB-5C39399514C6 triggers a BINARY -> UTF-8 conversion error
|
648
650
|
def unescape(right)
|
649
|
-
right
|
651
|
+
right = right.to_s
|
652
|
+
if right.length == 16 && right.encoding == Encoding::BINARY
|
653
|
+
right
|
654
|
+
else
|
655
|
+
right.to_s.gsub(/\\([a-fA-F\d]{2})/) { [$1.hex].pack("U") }
|
656
|
+
end
|
650
657
|
end
|
651
658
|
private :unescape
|
652
659
|
|
@@ -748,7 +755,7 @@ class Net::LDAP::Filter
|
|
748
755
|
# This parses a given expression inside of parentheses.
|
749
756
|
def parse_filter_branch(scanner)
|
750
757
|
scanner.scan(/\s*/)
|
751
|
-
if token = scanner.scan(/[-\w
|
758
|
+
if token = scanner.scan(/[-\w:.;]*[\w]/)
|
752
759
|
scanner.scan(/\s*/)
|
753
760
|
if op = scanner.scan(/<=|>=|!=|:=|=/)
|
754
761
|
scanner.scan(/\s*/)
|
@@ -12,8 +12,8 @@ module Net::LDAP::Instrumentation
|
|
12
12
|
def instrument(event, payload = {})
|
13
13
|
payload = (payload || {}).dup
|
14
14
|
if instrumentation_service
|
15
|
-
instrumentation_service.instrument(event, payload) do |
|
16
|
-
|
15
|
+
instrumentation_service.instrument(event, payload) do |instr_payload|
|
16
|
+
instr_payload[:result] = yield(instr_payload) if block_given?
|
17
17
|
end
|
18
18
|
else
|
19
19
|
yield(payload) if block_given?
|