net-ldap 0.12.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/Contributors.rdoc +1 -0
  3. data/History.rdoc +89 -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 +163 -50
  15. data/lib/net/ldap/dataset.rb +5 -5
  16. data/lib/net/ldap/dn.rb +13 -14
  17. data/lib/net/ldap/entry.rb +17 -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 +209 -90
  25. data/lib/net/snmp.rb +19 -19
  26. data/lib/net-ldap.rb +1 -1
  27. metadata +30 -99
  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
@@ -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.17.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
@@ -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,73 @@ 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
+ # * :encryption => specifies the encryption to be used in communicating
484
+ # with the LDAP server. The value must be a Hash containing additional
485
+ # parameters, which consists of two keys:
486
+ # method: - :simple_tls or :start_tls
487
+ # tls_options: - Hash of options for that method
488
+ # The :simple_tls encryption method encrypts <i>all</i> communications
489
+ # with the LDAP server. It completely establishes SSL/TLS encryption with
490
+ # the LDAP server before any LDAP-protocol data is exchanged. There is no
491
+ # plaintext negotiation and no special encryption-request controls are
492
+ # sent to the server. <i>The :simple_tls option is the simplest, easiest
493
+ # way to encrypt communications between Net::LDAP and LDAP servers.</i>
494
+ # If you get communications or protocol errors when using this option,
495
+ # check with your LDAP server administrator. Pay particular attention
496
+ # to the TCP port you are connecting to. It's impossible for an LDAP
497
+ # server to support plaintext LDAP communications and <i>simple TLS</i>
498
+ # connections on the same port. The standard TCP port for unencrypted
499
+ # LDAP connections is 389, but the standard port for simple-TLS
500
+ # encrypted connections is 636. Be sure you are using the correct port.
501
+ # The :start_tls like the :simple_tls encryption method also encrypts all
502
+ # communcations with the LDAP server. With the exception that it operates
503
+ # over the standard TCP port.
504
+ #
505
+ # To validate the LDAP server's certificate (a security must if you're
506
+ # talking over the public internet), you need to set :tls_options
507
+ # something like this...
508
+ #
509
+ # Net::LDAP.new(
510
+ # # ... set host, bind dn, etc ...
511
+ # encryption: {
512
+ # method: :simple_tls,
513
+ # tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS,
514
+ # }
515
+ # )
516
+ #
517
+ # The above will use the operating system-provided store of CA
518
+ # certificates to validate your LDAP server's cert.
519
+ # If cert validation fails, it'll happen during the #bind
520
+ # whenever you first try to open a connection to the server.
521
+ # Those methods will throw Net::LDAP::ConnectionError with
522
+ # a message about certificate verify failing. If your
523
+ # LDAP server's certificate is signed by DigiCert, Comodo, etc.,
524
+ # you're probably good. If you've got a self-signed cert but it's
525
+ # been added to the host's OS-maintained CA store (e.g. on Debian
526
+ # add foobar.crt to /usr/local/share/ca-certificates/ and run
527
+ # `update-ca-certificates`), then the cert should pass validation.
528
+ # To ignore the OS's CA store, put your CA in a PEM-encoded file and...
529
+ #
530
+ # encryption: {
531
+ # method: :simple_tls,
532
+ # tls_options: { ca_file: '/path/to/my-little-ca.pem',
533
+ # ssl_version: 'TLSv1_1' },
534
+ # }
535
+ #
536
+ # As you might guess, the above example also fails the connection
537
+ # if the client can't negotiate TLS v1.1.
538
+ # tls_options is ultimately passed to OpenSSL::SSL::SSLContext#set_params
539
+ # For more details, see
540
+ # http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html
473
541
  #
474
542
  # Instantiating a Net::LDAP object does <i>not</i> result in network
475
543
  # traffic to the LDAP server. It simply stores the connection and binding
476
- # parameters in the object.
544
+ # parameters in the object. That's why Net::LDAP.new doesn't throw
545
+ # cert validation errors itself; #bind does instead.
477
546
  def initialize(args = {})
478
547
  @host = args[:host] || DefaultHost
479
548
  @port = args[:port] || DefaultPort
@@ -482,7 +551,8 @@ class Net::LDAP
482
551
  @auth = args[:auth] || DefaultAuth
483
552
  @base = args[:base] || DefaultTreebase
484
553
  @force_no_page = args[:force_no_page] || DefaultForceNoPage
485
- encryption args[:encryption] # may be nil
554
+ @encryption = normalize_encryption(args[:encryption]) # may be nil
555
+ @connect_timeout = args[:connect_timeout]
486
556
 
487
557
  if pr = @auth[:password] and pr.respond_to?(:call)
488
558
  @auth[:password] = pr.call
@@ -533,7 +603,7 @@ class Net::LDAP
533
603
  @auth = {
534
604
  :method => :simple,
535
605
  :username => username,
536
- :password => password
606
+ :password => password,
537
607
  }
538
608
  end
539
609
  alias_method :auth, :authenticate
@@ -546,54 +616,12 @@ class Net::LDAP
546
616
  # additional capabilities are added, more configuration values will be
547
617
  # added here.
548
618
  #
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:
619
+ # This method is deprecated.
586
620
  #
587
- # {
588
- # :method => :start_tls,
589
- # :tls_options => { :ca_file => "/etc/cafile.pem", :ssl_version => "TLSv1_1" }
590
- # }
591
621
  def encryption(args)
592
- case args
593
- when :simple_tls, :start_tls
594
- args = { :method => args, :tls_options => {} }
595
- end
596
- @encryption = args
622
+ warn "Deprecation warning: please give :encryption option as a Hash to Net::LDAP.new"
623
+ return if args.nil?
624
+ @encryption = normalize_encryption(args)
597
625
  end
598
626
 
599
627
  # #open takes the same parameters as #new. #open makes a network
@@ -637,8 +665,11 @@ class Net::LDAP
637
665
  #++
638
666
  def get_operation_result
639
667
  result = @result
640
- result = result.result if result.is_a?(Net::LDAP::PDU)
641
668
  os = OpenStruct.new
669
+ if result.is_a?(Net::LDAP::PDU)
670
+ os.extended_response = result.extended_response
671
+ result = result.result
672
+ end
642
673
  if result.is_a?(Hash)
643
674
  # We might get a hash of LDAP response codes instead of a simple
644
675
  # numeric code.
@@ -681,7 +712,7 @@ class Net::LDAP
681
712
  begin
682
713
  @open_connection = new_connection
683
714
  payload[:connection] = @open_connection
684
- payload[:bind] = @open_connection.bind(@auth)
715
+ payload[:bind] = @result = @open_connection.bind(@auth)
685
716
  yield self
686
717
  ensure
687
718
  @open_connection.close if @open_connection
@@ -750,10 +781,10 @@ class Net::LDAP
750
781
 
751
782
  instrument "search.net_ldap", args do |payload|
752
783
  @result = use_connection(args) do |conn|
753
- conn.search(args) { |entry|
784
+ conn.search(args) do |entry|
754
785
  result_set << entry if result_set
755
786
  yield entry if block_given?
756
- }
787
+ end
757
788
  end
758
789
 
759
790
  if return_result_set
@@ -892,7 +923,7 @@ class Net::LDAP
892
923
  # end
893
924
  def bind_as(args = {})
894
925
  result = false
895
- open { |me|
926
+ open do |me|
896
927
  rs = search args
897
928
  if rs and rs.first and dn = rs.first.dn
898
929
  password = args[:password]
@@ -900,7 +931,7 @@ class Net::LDAP
900
931
  result = rs if bind(:method => :simple, :username => dn,
901
932
  :password => password)
902
933
  end
903
- }
934
+ end
904
935
  result
905
936
  end
906
937
 
@@ -1027,6 +1058,44 @@ class Net::LDAP
1027
1058
  end
1028
1059
  end
1029
1060
 
1061
+ # Password Modify
1062
+ #
1063
+ # Change existing password:
1064
+ #
1065
+ # dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com'
1066
+ # auth = {
1067
+ # method: :simple,
1068
+ # username: dn,
1069
+ # password: 'passworD1'
1070
+ # }
1071
+ # ldap.password_modify(dn: dn,
1072
+ # auth: auth,
1073
+ # old_password: 'passworD1',
1074
+ # new_password: 'passworD2')
1075
+ #
1076
+ # Or get the LDAP server to generate a password for you:
1077
+ #
1078
+ # dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com'
1079
+ # auth = {
1080
+ # method: :simple,
1081
+ # username: dn,
1082
+ # password: 'passworD1'
1083
+ # }
1084
+ # ldap.password_modify(dn: dn,
1085
+ # auth: auth,
1086
+ # old_password: 'passworD1')
1087
+ #
1088
+ # ldap.get_operation_result.extended_response[0][0] #=> 'VtcgGf/G'
1089
+ #
1090
+ def password_modify(args)
1091
+ instrument "modify_password.net_ldap", args do |payload|
1092
+ @result = use_connection(args) do |conn|
1093
+ conn.password_modify(args)
1094
+ end
1095
+ @result.success?
1096
+ end
1097
+ end
1098
+
1030
1099
  # Add a value to an attribute. Takes the full DN of the entry to modify,
1031
1100
  # the name (Symbol or String) of the attribute, and the value (String or
1032
1101
  # Array). If the attribute does not exist (and there are no schema
@@ -1113,14 +1182,22 @@ class Net::LDAP
1113
1182
  # entries. This method sends an extra control code to tell the LDAP server
1114
1183
  # to do a tree delete. ('1.2.840.113556.1.4.805')
1115
1184
  #
1185
+ # If the LDAP server does not support the DELETE_TREE control code, subordinate
1186
+ # entries are deleted recursively instead.
1187
+ #
1116
1188
  # Returns True or False to indicate whether the delete succeeded. Extended
1117
1189
  # status information is available by calling #get_operation_result.
1118
1190
  #
1119
1191
  # dn = "mail=deleteme@example.com, ou=people, dc=example, dc=com"
1120
1192
  # ldap.delete_tree :dn => dn
1121
1193
  def delete_tree(args)
1122
- delete(args.merge(:control_codes => [[Net::LDAP::LDAPControls::DELETE_TREE, true]]))
1194
+ if search_root_dse[:supportedcontrol].include? Net::LDAP::LDAPControls::DELETE_TREE
1195
+ delete(args.merge(:control_codes => [[Net::LDAP::LDAPControls::DELETE_TREE, true]]))
1196
+ else
1197
+ recursive_delete(args)
1198
+ end
1123
1199
  end
1200
+
1124
1201
  # This method is experimental and subject to change. Return the rootDSE
1125
1202
  # record from the LDAP server as a Net::LDAP::Entry, or an empty Entry if
1126
1203
  # the server doesn't return the record.
@@ -1145,7 +1222,7 @@ class Net::LDAP
1145
1222
  :supportedExtension,
1146
1223
  :supportedFeatures,
1147
1224
  :supportedLdapVersion,
1148
- :supportedSASLMechanisms
1225
+ :supportedSASLMechanisms,
1149
1226
  ])
1150
1227
  (rs and rs.first) or Net::LDAP::Entry.new
1151
1228
  end
@@ -1212,6 +1289,11 @@ class Net::LDAP
1212
1289
  inspected
1213
1290
  end
1214
1291
 
1292
+ # Internal: Set @open_connection for testing
1293
+ def connection=(connection)
1294
+ @open_connection = connection
1295
+ end
1296
+
1215
1297
  private
1216
1298
 
1217
1299
  # Yields an open connection if there is one, otherwise establishes a new
@@ -1224,11 +1306,9 @@ class Net::LDAP
1224
1306
  else
1225
1307
  begin
1226
1308
  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
1309
+ result = conn.bind(args[:auth] || @auth)
1310
+ return result unless result.result_code == Net::LDAP::ResultCodeSuccess
1311
+ yield conn
1232
1312
  ensure
1233
1313
  conn.close if conn
1234
1314
  end
@@ -1237,11 +1317,50 @@ class Net::LDAP
1237
1317
 
1238
1318
  # Establish a new connection to the LDAP server
1239
1319
  def new_connection
1240
- Net::LDAP::Connection.new \
1320
+ connection = Net::LDAP::Connection.new \
1241
1321
  :host => @host,
1242
1322
  :port => @port,
1243
1323
  :hosts => @hosts,
1244
1324
  :encryption => @encryption,
1245
- :instrumentation_service => @instrumentation_service
1325
+ :instrumentation_service => @instrumentation_service,
1326
+ :connect_timeout => @connect_timeout
1327
+
1328
+ # Force connect to see if there's a connection error
1329
+ connection.socket
1330
+ connection
1331
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
1332
+ @result = {
1333
+ :resultCode => 52,
1334
+ :errorMessage => ResultStrings[ResultCodeUnavailable],
1335
+ }
1336
+ raise e
1337
+ end
1338
+
1339
+ # Normalize encryption parameter the constructor accepts, expands a few
1340
+ # convenience symbols into recognizable hashes
1341
+ def normalize_encryption(args)
1342
+ return if args.nil?
1343
+ return args if args.is_a? Hash
1344
+
1345
+ case method = args.to_sym
1346
+ when :simple_tls, :start_tls
1347
+ { :method => method, :tls_options => {} }
1348
+ end
1349
+ end
1350
+
1351
+ # Recursively delete a dn and it's subordinate children.
1352
+ # This is useful when a server does not support the DELETE_TREE control code.
1353
+ def recursive_delete(args)
1354
+ raise EmptyDNError unless args.is_a?(Hash) && args.key?(:dn)
1355
+ # Delete Children
1356
+ search(base: args[:dn], scope: Net::LDAP::SearchScope_SingleLevel) do |entry|
1357
+ recursive_delete(dn: entry.dn)
1358
+ end
1359
+ # Delete Self
1360
+ unless delete(dn: args[:dn])
1361
+ raise Net::LDAP::Error, get_operation_result[:error_message].to_s
1362
+ end
1363
+ true
1246
1364
  end
1365
+
1247
1366
  end # class LDAP