net-ldap 0.12.0 → 0.19.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 +112 -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 +173 -52
- data/lib/net/ldap/dataset.rb +3 -5
- data/lib/net/ldap/dn.rb +21 -30
- data/lib/net/ldap/entry.rb +15 -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 +212 -91
- data/lib/net/snmp.rb +19 -19
- data/lib/net-ldap.rb +1 -1
- metadata +27 -96
- 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/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?
|
data/lib/net/ldap/password.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- ruby encoding: utf-8 -*-
|
2
2
|
require 'digest/sha1'
|
3
|
+
require 'digest/sha2'
|
3
4
|
require 'digest/md5'
|
4
5
|
require 'base64'
|
5
6
|
require 'securerandom'
|
@@ -19,20 +20,21 @@ class Net::LDAP::Password
|
|
19
20
|
# * Should we provide sha1 as a synonym for sha1? I vote no because then
|
20
21
|
# should you also provide ssha1 for symmetry?
|
21
22
|
#
|
22
|
-
attribute_value = ""
|
23
23
|
def generate(type, str)
|
24
24
|
case type
|
25
25
|
when :md5
|
26
|
-
|
26
|
+
'{MD5}' + Base64.strict_encode64(Digest::MD5.digest(str))
|
27
27
|
when :sha
|
28
|
-
|
28
|
+
'{SHA}' + Base64.strict_encode64(Digest::SHA1.digest(str))
|
29
29
|
when :ssha
|
30
30
|
salt = SecureRandom.random_bytes(16)
|
31
|
-
|
31
|
+
'{SSHA}' + Base64.strict_encode64(Digest::SHA1.digest(str + salt) + salt)
|
32
|
+
when :ssha256
|
33
|
+
salt = SecureRandom.random_bytes(16)
|
34
|
+
'{SSHA256}' + Base64.strict_encode64(Digest::SHA256.digest(str + salt) + salt)
|
32
35
|
else
|
33
36
|
raise Net::LDAP::HashTypeUnsupportedError, "Unsupported password-hash type (#{type})"
|
34
37
|
end
|
35
|
-
return attribute_value
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
data/lib/net/ldap/pdu.rb
CHANGED
@@ -74,6 +74,7 @@ class Net::LDAP::PDU
|
|
74
74
|
attr_reader :search_referrals
|
75
75
|
attr_reader :search_parameters
|
76
76
|
attr_reader :bind_parameters
|
77
|
+
attr_reader :extended_response
|
77
78
|
|
78
79
|
##
|
79
80
|
# Returns RFC-2251 Controls if any.
|
@@ -120,9 +121,9 @@ class Net::LDAP::PDU
|
|
120
121
|
when UnbindRequest
|
121
122
|
parse_unbind_request(ber_object[1])
|
122
123
|
when ExtendedResponse
|
123
|
-
|
124
|
+
parse_extended_response(ber_object[1])
|
124
125
|
else
|
125
|
-
raise
|
126
|
+
raise Error.new("unknown pdu-type: #{@app_tag}")
|
126
127
|
end
|
127
128
|
|
128
129
|
parse_controls(ber_object[2]) if ber_object[2]
|
@@ -174,12 +175,35 @@ class Net::LDAP::PDU
|
|
174
175
|
@ldap_result = {
|
175
176
|
:resultCode => sequence[0],
|
176
177
|
:matchedDN => sequence[1],
|
177
|
-
:errorMessage => sequence[2]
|
178
|
+
:errorMessage => sequence[2],
|
178
179
|
}
|
179
180
|
parse_search_referral(sequence[3]) if @ldap_result[:resultCode] == Net::LDAP::ResultCodeReferral
|
180
181
|
end
|
181
182
|
private :parse_ldap_result
|
182
183
|
|
184
|
+
##
|
185
|
+
# Parse an extended response
|
186
|
+
#
|
187
|
+
# http://www.ietf.org/rfc/rfc2251.txt
|
188
|
+
#
|
189
|
+
# Each Extended operation consists of an Extended request and an
|
190
|
+
# Extended response.
|
191
|
+
#
|
192
|
+
# ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
|
193
|
+
# requestName [0] LDAPOID,
|
194
|
+
# requestValue [1] OCTET STRING OPTIONAL }
|
195
|
+
|
196
|
+
def parse_extended_response(sequence)
|
197
|
+
sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP result length."
|
198
|
+
@ldap_result = {
|
199
|
+
:resultCode => sequence[0],
|
200
|
+
:matchedDN => sequence[1],
|
201
|
+
:errorMessage => sequence[2],
|
202
|
+
}
|
203
|
+
@extended_response = sequence[3]
|
204
|
+
end
|
205
|
+
private :parse_extended_response
|
206
|
+
|
183
207
|
##
|
184
208
|
# A Bind Response may have an additional field, ID [7], serverSaslCreds,
|
185
209
|
# per RFC 2251 pgh 4.2.3.
|
data/lib/net/ldap/version.rb
CHANGED
data/lib/net/ldap.rb
CHANGED
@@ -17,19 +17,19 @@ module Net # :nodoc:
|
|
17
17
|
end
|
18
18
|
require 'socket'
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
20
|
+
require_relative 'ber'
|
21
|
+
require_relative 'ldap/pdu'
|
22
|
+
require_relative 'ldap/filter'
|
23
|
+
require_relative 'ldap/dataset'
|
24
|
+
require_relative 'ldap/password'
|
25
|
+
require_relative 'ldap/entry'
|
26
|
+
require_relative 'ldap/instrumentation'
|
27
|
+
require_relative 'ldap/connection'
|
28
|
+
require_relative 'ldap/version'
|
29
|
+
require_relative 'ldap/error'
|
30
|
+
require_relative 'ldap/auth_adapter'
|
31
|
+
require_relative 'ldap/auth_adapter/simple'
|
32
|
+
require_relative 'ldap/auth_adapter/sasl'
|
33
33
|
|
34
34
|
Net::LDAP::AuthAdapter.register([:simple, :anon, :anonymous], Net::LDAP::AuthAdapter::Simple)
|
35
35
|
Net::LDAP::AuthAdapter.register(:sasl, Net::LDAP::AuthAdapter::Sasl)
|
@@ -79,6 +79,14 @@ Net::LDAP::AuthAdapter.register(:sasl, Net::LDAP::AuthAdapter::Sasl)
|
|
79
79
|
#
|
80
80
|
# p ldap.get_operation_result
|
81
81
|
#
|
82
|
+
# === Setting connect timeout
|
83
|
+
#
|
84
|
+
# By default, Net::LDAP uses TCP sockets with a connection timeout of 5 seconds.
|
85
|
+
#
|
86
|
+
# This value can be tweaked passing the :connect_timeout parameter.
|
87
|
+
# i.e.
|
88
|
+
# ldap = Net::LDAP.new ...,
|
89
|
+
# :connect_timeout => 3
|
82
90
|
#
|
83
91
|
# == A Brief Introduction to LDAP
|
84
92
|
#
|
@@ -256,14 +264,14 @@ class Net::LDAP
|
|
256
264
|
SearchScope_BaseObject = 0
|
257
265
|
SearchScope_SingleLevel = 1
|
258
266
|
SearchScope_WholeSubtree = 2
|
259
|
-
SearchScopes = [
|
260
|
-
SearchScope_WholeSubtree
|
267
|
+
SearchScopes = [SearchScope_BaseObject, SearchScope_SingleLevel,
|
268
|
+
SearchScope_WholeSubtree]
|
261
269
|
|
262
270
|
DerefAliases_Never = 0
|
263
271
|
DerefAliases_Search = 1
|
264
272
|
DerefAliases_Find = 2
|
265
273
|
DerefAliases_Always = 3
|
266
|
-
DerefAliasesArray = [
|
274
|
+
DerefAliasesArray = [DerefAliases_Never, DerefAliases_Search, DerefAliases_Find, DerefAliases_Always]
|
267
275
|
|
268
276
|
primitive = { 2 => :null } # UnbindRequest body
|
269
277
|
constructed = {
|
@@ -315,7 +323,14 @@ class Net::LDAP
|
|
315
323
|
:constructed => constructed,
|
316
324
|
}
|
317
325
|
|
326
|
+
universal = {
|
327
|
+
constructed: {
|
328
|
+
107 => :array, #ExtendedResponse (PasswdModifyResponseValue)
|
329
|
+
},
|
330
|
+
}
|
331
|
+
|
318
332
|
AsnSyntax = Net::BER.compile_syntax(:application => application,
|
333
|
+
:universal => universal,
|
319
334
|
:context_specific => context_specific)
|
320
335
|
|
321
336
|
DefaultHost = "127.0.0.1"
|
@@ -324,7 +339,8 @@ class Net::LDAP
|
|
324
339
|
DefaultTreebase = "dc=com"
|
325
340
|
DefaultForceNoPage = false
|
326
341
|
|
327
|
-
StartTlsOid =
|
342
|
+
StartTlsOid = '1.3.6.1.4.1.1466.20037'
|
343
|
+
PasswdModifyOid = '1.3.6.1.4.1.4203.1.11.1'
|
328
344
|
|
329
345
|
# https://tools.ietf.org/html/rfc4511#section-4.1.9
|
330
346
|
# https://tools.ietf.org/html/rfc4511#appendix-A
|
@@ -373,14 +389,14 @@ class Net::LDAP
|
|
373
389
|
ResultCodeCompareFalse,
|
374
390
|
ResultCodeCompareTrue,
|
375
391
|
ResultCodeReferral,
|
376
|
-
ResultCodeSaslBindInProgress
|
392
|
+
ResultCodeSaslBindInProgress,
|
377
393
|
]
|
378
394
|
|
379
395
|
# nonstandard list of "successful" result codes for searches
|
380
396
|
ResultCodesSearchSuccess = [
|
381
397
|
ResultCodeSuccess,
|
382
398
|
ResultCodeTimeLimitExceeded,
|
383
|
-
ResultCodeSizeLimitExceeded
|
399
|
+
ResultCodeSizeLimitExceeded,
|
384
400
|
]
|
385
401
|
|
386
402
|
# map of result code to human message
|
@@ -396,7 +412,7 @@ class Net::LDAP
|
|
396
412
|
ResultCodeStrongerAuthRequired => "Stronger Auth Needed",
|
397
413
|
ResultCodeReferral => "Referral",
|
398
414
|
ResultCodeAdminLimitExceeded => "Admin Limit Exceeded",
|
399
|
-
ResultCodeUnavailableCriticalExtension => "Unavailable
|
415
|
+
ResultCodeUnavailableCriticalExtension => "Unavailable critical extension",
|
400
416
|
ResultCodeConfidentialityRequired => "Confidentiality Required",
|
401
417
|
ResultCodeSaslBindInProgress => "saslBindInProgress",
|
402
418
|
ResultCodeNoSuchAttribute => "No Such Attribute",
|
@@ -422,7 +438,7 @@ class Net::LDAP
|
|
422
438
|
ResultCodeEntryAlreadyExists => "Entry Already Exists",
|
423
439
|
ResultCodeObjectClassModsProhibited => "ObjectClass Modifications Prohibited",
|
424
440
|
ResultCodeAffectsMultipleDSAs => "Affects Multiple DSAs",
|
425
|
-
ResultCodeOther => "Other"
|
441
|
+
ResultCodeOther => "Other",
|
426
442
|
}
|
427
443
|
|
428
444
|
module LDAPControls
|
@@ -460,20 +476,75 @@ class Net::LDAP
|
|
460
476
|
# specify a treebase. If you give a treebase value in any particular
|
461
477
|
# call to #search, that value will override any treebase value you give
|
462
478
|
# here.
|
463
|
-
# * :encryption => specifies the encryption to be used in communicating
|
464
|
-
# with the LDAP server. The value is either a Hash containing additional
|
465
|
-
# parameters, or the Symbol :simple_tls, which is equivalent to
|
466
|
-
# specifying the Hash {:method => :simple_tls}. There is a fairly large
|
467
|
-
# range of potential values that may be given for this parameter. See
|
468
|
-
# #encryption for details.
|
469
479
|
# * :force_no_page => Set to true to prevent paged results even if your
|
470
480
|
# server says it supports them. This is a fix for MS Active Directory
|
471
481
|
# * :instrumentation_service => An object responsible for instrumenting
|
472
482
|
# operations, compatible with ActiveSupport::Notifications' public API.
|
483
|
+
# * :connect_timeout => The TCP socket timeout (in seconds) to use when
|
484
|
+
# connecting to the LDAP server (default 5 seconds).
|
485
|
+
# * :encryption => specifies the encryption to be used in communicating
|
486
|
+
# with the LDAP server. The value must be a Hash containing additional
|
487
|
+
# parameters, which consists of two keys:
|
488
|
+
# method: - :simple_tls or :start_tls
|
489
|
+
# tls_options: - Hash of options for that method
|
490
|
+
# The :simple_tls encryption method encrypts <i>all</i> communications
|
491
|
+
# with the LDAP server. It completely establishes SSL/TLS encryption with
|
492
|
+
# the LDAP server before any LDAP-protocol data is exchanged. There is no
|
493
|
+
# plaintext negotiation and no special encryption-request controls are
|
494
|
+
# sent to the server. <i>The :simple_tls option is the simplest, easiest
|
495
|
+
# way to encrypt communications between Net::LDAP and LDAP servers.</i>
|
496
|
+
# If you get communications or protocol errors when using this option,
|
497
|
+
# check with your LDAP server administrator. Pay particular attention
|
498
|
+
# to the TCP port you are connecting to. It's impossible for an LDAP
|
499
|
+
# server to support plaintext LDAP communications and <i>simple TLS</i>
|
500
|
+
# connections on the same port. The standard TCP port for unencrypted
|
501
|
+
# LDAP connections is 389, but the standard port for simple-TLS
|
502
|
+
# encrypted connections is 636. Be sure you are using the correct port.
|
503
|
+
# The :start_tls like the :simple_tls encryption method also encrypts all
|
504
|
+
# communcations with the LDAP server. With the exception that it operates
|
505
|
+
# over the standard TCP port.
|
506
|
+
#
|
507
|
+
# To validate the LDAP server's certificate (a security must if you're
|
508
|
+
# talking over the public internet), you need to set :tls_options
|
509
|
+
# something like this...
|
510
|
+
#
|
511
|
+
# Net::LDAP.new(
|
512
|
+
# # ... set host, bind dn, etc ...
|
513
|
+
# encryption: {
|
514
|
+
# method: :simple_tls,
|
515
|
+
# tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS,
|
516
|
+
# }
|
517
|
+
# )
|
518
|
+
#
|
519
|
+
# The above will use the operating system-provided store of CA
|
520
|
+
# certificates to validate your LDAP server's cert.
|
521
|
+
# If cert validation fails, it'll happen during the #bind
|
522
|
+
# whenever you first try to open a connection to the server.
|
523
|
+
# Those methods will throw Net::LDAP::ConnectionError with
|
524
|
+
# a message about certificate verify failing. If your
|
525
|
+
# LDAP server's certificate is signed by DigiCert, Comodo, etc.,
|
526
|
+
# you're probably good. If you've got a self-signed cert but it's
|
527
|
+
# been added to the host's OS-maintained CA store (e.g. on Debian
|
528
|
+
# add foobar.crt to /usr/local/share/ca-certificates/ and run
|
529
|
+
# `update-ca-certificates`), then the cert should pass validation.
|
530
|
+
# To ignore the OS's CA store, put your CA in a PEM-encoded file and...
|
531
|
+
#
|
532
|
+
# encryption: {
|
533
|
+
# method: :simple_tls,
|
534
|
+
# tls_options: { ca_file: '/path/to/my-little-ca.pem',
|
535
|
+
# ssl_version: 'TLSv1_1' },
|
536
|
+
# }
|
537
|
+
#
|
538
|
+
# As you might guess, the above example also fails the connection
|
539
|
+
# if the client can't negotiate TLS v1.1.
|
540
|
+
# tls_options is ultimately passed to OpenSSL::SSL::SSLContext#set_params
|
541
|
+
# For more details, see
|
542
|
+
# http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html
|
473
543
|
#
|
474
544
|
# Instantiating a Net::LDAP object does <i>not</i> result in network
|
475
545
|
# traffic to the LDAP server. It simply stores the connection and binding
|
476
|
-
# parameters in the object.
|
546
|
+
# parameters in the object. That's why Net::LDAP.new doesn't throw
|
547
|
+
# cert validation errors itself; #bind does instead.
|
477
548
|
def initialize(args = {})
|
478
549
|
@host = args[:host] || DefaultHost
|
479
550
|
@port = args[:port] || DefaultPort
|
@@ -482,7 +553,8 @@ class Net::LDAP
|
|
482
553
|
@auth = args[:auth] || DefaultAuth
|
483
554
|
@base = args[:base] || DefaultTreebase
|
484
555
|
@force_no_page = args[:force_no_page] || DefaultForceNoPage
|
485
|
-
encryption args[:encryption] # may be nil
|
556
|
+
@encryption = normalize_encryption(args[:encryption]) # may be nil
|
557
|
+
@connect_timeout = args[:connect_timeout]
|
486
558
|
|
487
559
|
if pr = @auth[:password] and pr.respond_to?(:call)
|
488
560
|
@auth[:password] = pr.call
|
@@ -533,7 +605,7 @@ class Net::LDAP
|
|
533
605
|
@auth = {
|
534
606
|
:method => :simple,
|
535
607
|
:username => username,
|
536
|
-
:password => password
|
608
|
+
:password => password,
|
537
609
|
}
|
538
610
|
end
|
539
611
|
alias_method :auth, :authenticate
|
@@ -546,54 +618,12 @@ class Net::LDAP
|
|
546
618
|
# additional capabilities are added, more configuration values will be
|
547
619
|
# added here.
|
548
620
|
#
|
549
|
-
#
|
550
|
-
# with the LDAP server. It completely establishes SSL/TLS encryption with
|
551
|
-
# the LDAP server before any LDAP-protocol data is exchanged. There is no
|
552
|
-
# plaintext negotiation and no special encryption-request controls are
|
553
|
-
# sent to the server. <i>The :simple_tls option is the simplest, easiest
|
554
|
-
# way to encrypt communications between Net::LDAP and LDAP servers.</i>
|
555
|
-
# It's intended for cases where you have an implicit level of trust in the
|
556
|
-
# authenticity of the LDAP server. No validation of the LDAP server's SSL
|
557
|
-
# certificate is performed. This means that :simple_tls will not produce
|
558
|
-
# errors if the LDAP server's encryption certificate is not signed by a
|
559
|
-
# well-known Certification Authority. If you get communications or
|
560
|
-
# protocol errors when using this option, check with your LDAP server
|
561
|
-
# administrator. Pay particular attention to the TCP port you are
|
562
|
-
# connecting to. It's impossible for an LDAP server to support plaintext
|
563
|
-
# LDAP communications and <i>simple TLS</i> connections on the same port.
|
564
|
-
# The standard TCP port for unencrypted LDAP connections is 389, but the
|
565
|
-
# standard port for simple-TLS encrypted connections is 636. Be sure you
|
566
|
-
# are using the correct port.
|
567
|
-
#
|
568
|
-
# The :start_tls like the :simple_tls encryption method also encrypts all
|
569
|
-
# communcations with the LDAP server. With the exception that it operates
|
570
|
-
# over the standard TCP port.
|
571
|
-
#
|
572
|
-
# In order to verify certificates and enable other TLS options, the
|
573
|
-
# :tls_options hash can be passed alongside :simple_tls or :start_tls.
|
574
|
-
# This hash contains any options that can be passed to
|
575
|
-
# OpenSSL::SSL::SSLContext#set_params(). The most common options passed
|
576
|
-
# should be OpenSSL::SSL::SSLContext::DEFAULT_PARAMS, or the :ca_file option,
|
577
|
-
# which contains a path to a Certificate Authority file (PEM-encoded).
|
578
|
-
#
|
579
|
-
# Example for a default setup without custom settings:
|
580
|
-
# {
|
581
|
-
# :method => :simple_tls,
|
582
|
-
# :tls_options => OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
583
|
-
# }
|
584
|
-
#
|
585
|
-
# Example for specifying a CA-File and only allowing TLSv1.1 connections:
|
621
|
+
# This method is deprecated.
|
586
622
|
#
|
587
|
-
# {
|
588
|
-
# :method => :start_tls,
|
589
|
-
# :tls_options => { :ca_file => "/etc/cafile.pem", :ssl_version => "TLSv1_1" }
|
590
|
-
# }
|
591
623
|
def encryption(args)
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
end
|
596
|
-
@encryption = args
|
624
|
+
warn "Deprecation warning: please give :encryption option as a Hash to Net::LDAP.new"
|
625
|
+
return if args.nil?
|
626
|
+
@encryption = normalize_encryption(args)
|
597
627
|
end
|
598
628
|
|
599
629
|
# #open takes the same parameters as #new. #open makes a network
|
@@ -637,8 +667,11 @@ class Net::LDAP
|
|
637
667
|
#++
|
638
668
|
def get_operation_result
|
639
669
|
result = @result
|
640
|
-
result = result.result if result.is_a?(Net::LDAP::PDU)
|
641
670
|
os = OpenStruct.new
|
671
|
+
if result.is_a?(Net::LDAP::PDU)
|
672
|
+
os.extended_response = result.extended_response
|
673
|
+
result = result.result
|
674
|
+
end
|
642
675
|
if result.is_a?(Hash)
|
643
676
|
# We might get a hash of LDAP response codes instead of a simple
|
644
677
|
# numeric code.
|
@@ -681,7 +714,7 @@ class Net::LDAP
|
|
681
714
|
begin
|
682
715
|
@open_connection = new_connection
|
683
716
|
payload[:connection] = @open_connection
|
684
|
-
payload[:bind] = @open_connection.bind(@auth)
|
717
|
+
payload[:bind] = @result = @open_connection.bind(@auth)
|
685
718
|
yield self
|
686
719
|
ensure
|
687
720
|
@open_connection.close if @open_connection
|
@@ -750,10 +783,10 @@ class Net::LDAP
|
|
750
783
|
|
751
784
|
instrument "search.net_ldap", args do |payload|
|
752
785
|
@result = use_connection(args) do |conn|
|
753
|
-
conn.search(args)
|
786
|
+
conn.search(args) do |entry|
|
754
787
|
result_set << entry if result_set
|
755
788
|
yield entry if block_given?
|
756
|
-
|
789
|
+
end
|
757
790
|
end
|
758
791
|
|
759
792
|
if return_result_set
|
@@ -892,7 +925,7 @@ class Net::LDAP
|
|
892
925
|
# end
|
893
926
|
def bind_as(args = {})
|
894
927
|
result = false
|
895
|
-
open
|
928
|
+
open do |me|
|
896
929
|
rs = search args
|
897
930
|
if rs and rs.first and dn = rs.first.dn
|
898
931
|
password = args[:password]
|
@@ -900,7 +933,7 @@ class Net::LDAP
|
|
900
933
|
result = rs if bind(:method => :simple, :username => dn,
|
901
934
|
:password => password)
|
902
935
|
end
|
903
|
-
|
936
|
+
end
|
904
937
|
result
|
905
938
|
end
|
906
939
|
|
@@ -1027,6 +1060,44 @@ class Net::LDAP
|
|
1027
1060
|
end
|
1028
1061
|
end
|
1029
1062
|
|
1063
|
+
# Password Modify
|
1064
|
+
#
|
1065
|
+
# Change existing password:
|
1066
|
+
#
|
1067
|
+
# dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com'
|
1068
|
+
# auth = {
|
1069
|
+
# method: :simple,
|
1070
|
+
# username: dn,
|
1071
|
+
# password: 'passworD1'
|
1072
|
+
# }
|
1073
|
+
# ldap.password_modify(dn: dn,
|
1074
|
+
# auth: auth,
|
1075
|
+
# old_password: 'passworD1',
|
1076
|
+
# new_password: 'passworD2')
|
1077
|
+
#
|
1078
|
+
# Or get the LDAP server to generate a password for you:
|
1079
|
+
#
|
1080
|
+
# dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com'
|
1081
|
+
# auth = {
|
1082
|
+
# method: :simple,
|
1083
|
+
# username: dn,
|
1084
|
+
# password: 'passworD1'
|
1085
|
+
# }
|
1086
|
+
# ldap.password_modify(dn: dn,
|
1087
|
+
# auth: auth,
|
1088
|
+
# old_password: 'passworD1')
|
1089
|
+
#
|
1090
|
+
# ldap.get_operation_result.extended_response[0][0] #=> 'VtcgGf/G'
|
1091
|
+
#
|
1092
|
+
def password_modify(args)
|
1093
|
+
instrument "modify_password.net_ldap", args do |payload|
|
1094
|
+
@result = use_connection(args) do |conn|
|
1095
|
+
conn.password_modify(args)
|
1096
|
+
end
|
1097
|
+
@result.success?
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
|
1030
1101
|
# Add a value to an attribute. Takes the full DN of the entry to modify,
|
1031
1102
|
# the name (Symbol or String) of the attribute, and the value (String or
|
1032
1103
|
# Array). If the attribute does not exist (and there are no schema
|
@@ -1113,14 +1184,22 @@ class Net::LDAP
|
|
1113
1184
|
# entries. This method sends an extra control code to tell the LDAP server
|
1114
1185
|
# to do a tree delete. ('1.2.840.113556.1.4.805')
|
1115
1186
|
#
|
1187
|
+
# If the LDAP server does not support the DELETE_TREE control code, subordinate
|
1188
|
+
# entries are deleted recursively instead.
|
1189
|
+
#
|
1116
1190
|
# Returns True or False to indicate whether the delete succeeded. Extended
|
1117
1191
|
# status information is available by calling #get_operation_result.
|
1118
1192
|
#
|
1119
1193
|
# dn = "mail=deleteme@example.com, ou=people, dc=example, dc=com"
|
1120
1194
|
# ldap.delete_tree :dn => dn
|
1121
1195
|
def delete_tree(args)
|
1122
|
-
|
1196
|
+
if search_root_dse[:supportedcontrol].include? Net::LDAP::LDAPControls::DELETE_TREE
|
1197
|
+
delete(args.merge(:control_codes => [[Net::LDAP::LDAPControls::DELETE_TREE, true]]))
|
1198
|
+
else
|
1199
|
+
recursive_delete(args)
|
1200
|
+
end
|
1123
1201
|
end
|
1202
|
+
|
1124
1203
|
# This method is experimental and subject to change. Return the rootDSE
|
1125
1204
|
# record from the LDAP server as a Net::LDAP::Entry, or an empty Entry if
|
1126
1205
|
# the server doesn't return the record.
|
@@ -1145,7 +1224,7 @@ class Net::LDAP
|
|
1145
1224
|
:supportedExtension,
|
1146
1225
|
:supportedFeatures,
|
1147
1226
|
:supportedLdapVersion,
|
1148
|
-
:supportedSASLMechanisms
|
1227
|
+
:supportedSASLMechanisms,
|
1149
1228
|
])
|
1150
1229
|
(rs and rs.first) or Net::LDAP::Entry.new
|
1151
1230
|
end
|
@@ -1212,6 +1291,11 @@ class Net::LDAP
|
|
1212
1291
|
inspected
|
1213
1292
|
end
|
1214
1293
|
|
1294
|
+
# Internal: Set @open_connection for testing
|
1295
|
+
def connection=(connection)
|
1296
|
+
@open_connection = connection
|
1297
|
+
end
|
1298
|
+
|
1215
1299
|
private
|
1216
1300
|
|
1217
1301
|
# Yields an open connection if there is one, otherwise establishes a new
|
@@ -1224,11 +1308,9 @@ class Net::LDAP
|
|
1224
1308
|
else
|
1225
1309
|
begin
|
1226
1310
|
conn = new_connection
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
return result
|
1231
|
-
end
|
1311
|
+
result = conn.bind(args[:auth] || @auth)
|
1312
|
+
return result unless result.result_code == Net::LDAP::ResultCodeSuccess
|
1313
|
+
yield conn
|
1232
1314
|
ensure
|
1233
1315
|
conn.close if conn
|
1234
1316
|
end
|
@@ -1237,11 +1319,50 @@ class Net::LDAP
|
|
1237
1319
|
|
1238
1320
|
# Establish a new connection to the LDAP server
|
1239
1321
|
def new_connection
|
1240
|
-
Net::LDAP::Connection.new \
|
1322
|
+
connection = Net::LDAP::Connection.new \
|
1241
1323
|
:host => @host,
|
1242
1324
|
:port => @port,
|
1243
1325
|
:hosts => @hosts,
|
1244
1326
|
:encryption => @encryption,
|
1245
|
-
:instrumentation_service => @instrumentation_service
|
1327
|
+
:instrumentation_service => @instrumentation_service,
|
1328
|
+
:connect_timeout => @connect_timeout
|
1329
|
+
|
1330
|
+
# Force connect to see if there's a connection error
|
1331
|
+
connection.socket
|
1332
|
+
connection
|
1333
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
|
1334
|
+
@result = {
|
1335
|
+
:resultCode => 52,
|
1336
|
+
:errorMessage => ResultStrings[ResultCodeUnavailable],
|
1337
|
+
}
|
1338
|
+
raise e
|
1339
|
+
end
|
1340
|
+
|
1341
|
+
# Normalize encryption parameter the constructor accepts, expands a few
|
1342
|
+
# convenience symbols into recognizable hashes
|
1343
|
+
def normalize_encryption(args)
|
1344
|
+
return if args.nil?
|
1345
|
+
return args if args.is_a? Hash
|
1346
|
+
|
1347
|
+
case method = args.to_sym
|
1348
|
+
when :simple_tls, :start_tls
|
1349
|
+
{ :method => method, :tls_options => {} }
|
1350
|
+
end
|
1351
|
+
end
|
1352
|
+
|
1353
|
+
# Recursively delete a dn and it's subordinate children.
|
1354
|
+
# This is useful when a server does not support the DELETE_TREE control code.
|
1355
|
+
def recursive_delete(args)
|
1356
|
+
raise EmptyDNError unless args.is_a?(Hash) && args.key?(:dn)
|
1357
|
+
# Delete Children
|
1358
|
+
search(base: args[:dn], scope: Net::LDAP::SearchScope_SingleLevel) do |entry|
|
1359
|
+
recursive_delete(dn: entry.dn)
|
1360
|
+
end
|
1361
|
+
# Delete Self
|
1362
|
+
unless delete(dn: args[:dn])
|
1363
|
+
raise Net::LDAP::Error, get_operation_result[:error_message].to_s
|
1364
|
+
end
|
1365
|
+
true
|
1246
1366
|
end
|
1367
|
+
|
1247
1368
|
end # class LDAP
|