net-ldap 0.12.0 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|