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.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/Contributors.rdoc +1 -0
  3. data/History.rdoc +112 -0
  4. data/README.rdoc +19 -9
  5. data/lib/net/ber/ber_parser.rb +4 -4
  6. data/lib/net/ber/core_ext/array.rb +1 -1
  7. data/lib/net/ber/core_ext/integer.rb +1 -1
  8. data/lib/net/ber/core_ext/string.rb +1 -1
  9. data/lib/net/ber/core_ext.rb +6 -6
  10. data/lib/net/ber.rb +39 -9
  11. data/lib/net/ldap/auth_adapter/gss_spnego.rb +9 -8
  12. data/lib/net/ldap/auth_adapter/sasl.rb +6 -4
  13. data/lib/net/ldap/auth_adapter/simple.rb +1 -1
  14. data/lib/net/ldap/connection.rb +173 -52
  15. data/lib/net/ldap/dataset.rb +3 -5
  16. data/lib/net/ldap/dn.rb +21 -30
  17. data/lib/net/ldap/entry.rb +15 -7
  18. data/lib/net/ldap/error.rb +2 -25
  19. data/lib/net/ldap/filter.rb +15 -8
  20. data/lib/net/ldap/instrumentation.rb +2 -2
  21. data/lib/net/ldap/password.rb +7 -5
  22. data/lib/net/ldap/pdu.rb +27 -3
  23. data/lib/net/ldap/version.rb +1 -1
  24. data/lib/net/ldap.rb +212 -91
  25. data/lib/net/snmp.rb +19 -19
  26. data/lib/net-ldap.rb +1 -1
  27. metadata +27 -96
  28. data/.gitignore +0 -9
  29. data/.rubocop.yml +0 -5
  30. data/.rubocop_todo.yml +0 -462
  31. data/.travis.yml +0 -31
  32. data/CONTRIBUTING.md +0 -54
  33. data/Gemfile +0 -2
  34. data/Rakefile +0 -23
  35. data/net-ldap.gemspec +0 -36
  36. data/script/changelog +0 -47
  37. data/script/install-openldap +0 -112
  38. data/script/package +0 -7
  39. data/script/release +0 -16
  40. data/test/ber/core_ext/test_array.rb +0 -22
  41. data/test/ber/core_ext/test_string.rb +0 -25
  42. data/test/ber/test_ber.rb +0 -145
  43. data/test/fixtures/cacert.pem +0 -20
  44. data/test/fixtures/openldap/memberof.ldif +0 -33
  45. data/test/fixtures/openldap/retcode.ldif +0 -76
  46. data/test/fixtures/openldap/slapd.conf.ldif +0 -67
  47. data/test/fixtures/seed.ldif +0 -374
  48. data/test/integration/test_add.rb +0 -28
  49. data/test/integration/test_ber.rb +0 -30
  50. data/test/integration/test_bind.rb +0 -34
  51. data/test/integration/test_delete.rb +0 -31
  52. data/test/integration/test_open.rb +0 -88
  53. data/test/integration/test_return_codes.rb +0 -38
  54. data/test/integration/test_search.rb +0 -77
  55. data/test/support/vm/openldap/.gitignore +0 -1
  56. data/test/support/vm/openldap/README.md +0 -32
  57. data/test/support/vm/openldap/Vagrantfile +0 -33
  58. data/test/test_auth_adapter.rb +0 -11
  59. data/test/test_dn.rb +0 -44
  60. data/test/test_entry.rb +0 -65
  61. data/test/test_filter.rb +0 -223
  62. data/test/test_filter_parser.rb +0 -24
  63. data/test/test_helper.rb +0 -66
  64. data/test/test_ldap.rb +0 -67
  65. data/test/test_ldap_connection.rb +0 -460
  66. data/test/test_ldif.rb +0 -104
  67. data/test/test_password.rb +0 -10
  68. data/test/test_rename.rb +0 -77
  69. data/test/test_search.rb +0 -39
  70. data/test/test_snmp.rb +0 -119
  71. data/test/test_ssl_ber.rb +0 -40
  72. data/test/testdata.ldif +0 -101
  73. data/testserver/ldapserver.rb +0 -210
  74. data/testserver/testdata.ldif +0 -101
@@ -23,7 +23,7 @@
23
23
  class Net::LDAP::Filter
24
24
  ##
25
25
  # Known filter types.
26
- FilterTypes = [ :ne, :eq, :ge, :le, :and, :or, :not, :ex, :bineq ]
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 { |b|
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 {|a| a.to_ber}.to_ber_contextspecific(0)
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 {|a| a.to_ber}.to_ber_contextspecific(1)
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.to_s.gsub(/\\([a-fA-F\d]{2})/) { [$1.hex].pack("U") }
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:.]*[\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 |payload|
16
- payload[:result] = yield(payload) if block_given?
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?
@@ -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
- attribute_value = '{MD5}' + Base64.encode64(Digest::MD5.digest(str)).chomp!
26
+ '{MD5}' + Base64.strict_encode64(Digest::MD5.digest(str))
27
27
  when :sha
28
- attribute_value = '{SHA}' + Base64.encode64(Digest::SHA1.digest(str)).chomp!
28
+ '{SHA}' + Base64.strict_encode64(Digest::SHA1.digest(str))
29
29
  when :ssha
30
30
  salt = SecureRandom.random_bytes(16)
31
- attribute_value = '{SSHA}' + Base64.encode64(Digest::SHA1.digest(str + salt) + salt).chomp!
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
- parse_ldap_result(ber_object[1])
124
+ parse_extended_response(ber_object[1])
124
125
  else
125
- raise LdapPduError.new("unknown pdu-type: #{@app_tag}")
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.
@@ -1,5 +1,5 @@
1
1
  module Net
2
2
  class LDAP
3
- VERSION = "0.12.0"
3
+ VERSION = "0.19.0"
4
4
  end
5
5
  end
data/lib/net/ldap.rb CHANGED
@@ -17,19 +17,19 @@ module Net # :nodoc:
17
17
  end
18
18
  require 'socket'
19
19
 
20
- require 'net/ber'
21
- require 'net/ldap/pdu'
22
- require 'net/ldap/filter'
23
- require 'net/ldap/dataset'
24
- require 'net/ldap/password'
25
- require 'net/ldap/entry'
26
- require 'net/ldap/instrumentation'
27
- require 'net/ldap/connection'
28
- require 'net/ldap/version'
29
- require 'net/ldap/error'
30
- require 'net/ldap/auth_adapter'
31
- require 'net/ldap/auth_adapter/simple'
32
- require 'net/ldap/auth_adapter/sasl'
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 = [ SearchScope_BaseObject, SearchScope_SingleLevel,
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 = [ DerefAliases_Never, DerefAliases_Search, DerefAliases_Find, DerefAliases_Always ]
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 = "1.3.6.1.4.1.1466.20037"
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 crtical extension",
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
- # The :simple_tls encryption method encrypts <i>all</i> communications
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
- case args
593
- when :simple_tls, :start_tls
594
- args = { :method => args, :tls_options => {} }
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) { |entry|
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 { |me|
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
- delete(args.merge(:control_codes => [[Net::LDAP::LDAPControls::DELETE_TREE, true]]))
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
- if (result = conn.bind(args[:auth] || @auth)).result_code == Net::LDAP::ResultCodeSuccess
1228
- yield conn
1229
- else
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