net-ldap 0.13.0 → 0.14.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.

Potentially problematic release.


This version of net-ldap might be problematic. Click here for more details.

@@ -22,3 +22,4 @@ Contributions since:
22
22
  * David J. Lee (DavidJLee)
23
23
  * Cody Cutrer (ccutrer)
24
24
  * WoodsBagotAndreMarquesLee
25
+ * Rufus Post (mynameisrufus)
@@ -1,5 +1,22 @@
1
+ === Net::LDAP 0.14.0
2
+
3
+ * Normalize the encryption parameter passed to the LDAP constructor {#264}[https://github.com/ruby-ldap/ruby-net-ldap/pull/264]
4
+ * Update Docs: Net::LDAP now requires ruby >= 2 {#261}[https://github.com/ruby-ldap/ruby-net-ldap/pull/261]
5
+ * fix symbol proc {#255}[https://github.com/ruby-ldap/ruby-net-ldap/pull/255]
6
+ * fix trailing commas {#256}[https://github.com/ruby-ldap/ruby-net-ldap/pull/256]
7
+ * fix deprecated hash methods {#254}[https://github.com/ruby-ldap/ruby-net-ldap/pull/254]
8
+ * fix space after comma {#253}[https://github.com/ruby-ldap/ruby-net-ldap/pull/253]
9
+ * fix space inside brackets {#252}[https://github.com/ruby-ldap/ruby-net-ldap/pull/252]
10
+ * Rubocop style fixes {#249}[https://github.com/ruby-ldap/ruby-net-ldap/pull/249]
11
+ * Lazy initialize Net::LDAP::Connection's internal socket {#235}[https://github.com/ruby-ldap/ruby-net-ldap/pull/235]
12
+ * Support for rfc3062 Password Modify, closes #163 {#178}[https://github.com/ruby-ldap/ruby-net-ldap/pull/178]
13
+
1
14
  === Net::LDAP 0.13.0
2
15
 
16
+ Avoid this release for because of an backwards incompatibility in how encryption
17
+ is initialized https://github.com/ruby-ldap/ruby-net-ldap/pull/264. We did not
18
+ yank it because people have already worked around it.
19
+
3
20
  * Set a connect_timeout for the creation of a socket {#243}[https://github.com/ruby-ldap/ruby-net-ldap/pull/243]
4
21
  * Update bundler before installing gems with bundler {#245}[https://github.com/ruby-ldap/ruby-net-ldap/pull/245]
5
22
  * Net::LDAP#encryption accepts string {#239}[https://github.com/ruby-ldap/ruby-net-ldap/pull/239]
@@ -25,7 +25,7 @@ See Net::LDAP for documentation and usage samples.
25
25
 
26
26
  == Requirements
27
27
 
28
- Net::LDAP requires a Ruby 1.9.3 compatible interpreter or better.
28
+ Net::LDAP requires a Ruby 2.0.0 compatible interpreter or better.
29
29
 
30
30
  == Install
31
31
 
@@ -106,6 +106,7 @@ module Net # :nodoc:
106
106
  # <tr><th>CHARACTER STRING</th><th>C</th><td>29: 61 (0x3d, 0b00111101)</td></tr>
107
107
  # <tr><th>BMPString</th><th>P</th><td>30: 30 (0x1e, 0b00011110)</td></tr>
108
108
  # <tr><th>BMPString</th><th>C</th><td>30: 62 (0x3e, 0b00111110)</td></tr>
109
+ # <tr><th>ExtendedResponse</th><th>C</th><td>107: 139 (0x8b, 0b010001011)</td></tr>
109
110
  # </table>
110
111
  module BER
111
112
  VERSION = Net::LDAP::VERSION
@@ -234,7 +235,7 @@ module Net # :nodoc:
234
235
  # TODO 20100327 AZ: Should we be allocating an array of 256 values
235
236
  # that will either be +nil+ or an object type symbol, or should we
236
237
  # allocate an empty Hash since unknown values return +nil+ anyway?
237
- out = [ nil ] * 256
238
+ out = [nil] * 256
238
239
  syntax.each do |tag_class_id, encodings|
239
240
  tag_class = TAG_CLASS[tag_class_id]
240
241
  encodings.each do |encoding_id, classes|
@@ -269,7 +270,7 @@ class Net::BER::BerIdentifiedOid
269
270
 
270
271
  def initialize(oid)
271
272
  if oid.is_a?(String)
272
- oid = oid.split(/\./).map {|s| s.to_i }
273
+ oid = oid.split(/\./).map(&:to_i)
273
274
  end
274
275
  @value = oid
275
276
  end
@@ -14,7 +14,7 @@ module Net::BER::BERParser
14
14
  }
15
15
  constructed = {
16
16
  16 => :array,
17
- 17 => :array
17
+ 17 => :array,
18
18
  }
19
19
  universal = { :primitive => primitive, :constructed => constructed }
20
20
 
@@ -89,7 +89,7 @@ module Net::BER::Extensions::Array
89
89
  #if our array does not contain at least one array then wrap it in an array before going forward
90
90
  ary = self[0].kind_of?(Array) ? self : [self]
91
91
  ary = ary.collect do |control_sequence|
92
- control_sequence.collect{|element| element.to_ber}.to_ber_sequence.reject_empty_ber_arrays
92
+ control_sequence.collect(&:to_ber).to_ber_sequence.reject_empty_ber_arrays
93
93
  end
94
94
  ary.to_ber_sequence.reject_empty_ber_arrays
95
95
  end
@@ -20,7 +20,7 @@ module Net::BER::Extensions::Integer
20
20
  if self <= 127
21
21
  [self].pack('C')
22
22
  else
23
- i = [self].pack('N').sub(/^[\0]+/,"")
23
+ i = [self].pack('N').sub(/^[\0]+/, "")
24
24
  [0x80 + i.length].pack('C') + i
25
25
  end
26
26
  end
@@ -75,6 +75,6 @@ module Net::BER::Extensions::String
75
75
  end
76
76
 
77
77
  def reject_empty_ber_arrays
78
- self.gsub(/0\000/n,'')
78
+ self.gsub(/0\000/n, '')
79
79
  end
80
80
  end
@@ -264,14 +264,14 @@ class Net::LDAP
264
264
  SearchScope_BaseObject = 0
265
265
  SearchScope_SingleLevel = 1
266
266
  SearchScope_WholeSubtree = 2
267
- SearchScopes = [ SearchScope_BaseObject, SearchScope_SingleLevel,
268
- SearchScope_WholeSubtree ]
267
+ SearchScopes = [SearchScope_BaseObject, SearchScope_SingleLevel,
268
+ SearchScope_WholeSubtree]
269
269
 
270
270
  DerefAliases_Never = 0
271
271
  DerefAliases_Search = 1
272
272
  DerefAliases_Find = 2
273
273
  DerefAliases_Always = 3
274
- DerefAliasesArray = [ DerefAliases_Never, DerefAliases_Search, DerefAliases_Find, DerefAliases_Always ]
274
+ DerefAliasesArray = [DerefAliases_Never, DerefAliases_Search, DerefAliases_Find, DerefAliases_Always]
275
275
 
276
276
  primitive = { 2 => :null } # UnbindRequest body
277
277
  constructed = {
@@ -323,7 +323,14 @@ class Net::LDAP
323
323
  :constructed => constructed,
324
324
  }
325
325
 
326
+ universal = {
327
+ constructed: {
328
+ 107 => :array, #ExtendedResponse (PasswdModifyResponseValue)
329
+ },
330
+ }
331
+
326
332
  AsnSyntax = Net::BER.compile_syntax(:application => application,
333
+ :universal => universal,
327
334
  :context_specific => context_specific)
328
335
 
329
336
  DefaultHost = "127.0.0.1"
@@ -332,7 +339,8 @@ class Net::LDAP
332
339
  DefaultTreebase = "dc=com"
333
340
  DefaultForceNoPage = false
334
341
 
335
- 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'
336
344
 
337
345
  # https://tools.ietf.org/html/rfc4511#section-4.1.9
338
346
  # https://tools.ietf.org/html/rfc4511#appendix-A
@@ -381,14 +389,14 @@ class Net::LDAP
381
389
  ResultCodeCompareFalse,
382
390
  ResultCodeCompareTrue,
383
391
  ResultCodeReferral,
384
- ResultCodeSaslBindInProgress
392
+ ResultCodeSaslBindInProgress,
385
393
  ]
386
394
 
387
395
  # nonstandard list of "successful" result codes for searches
388
396
  ResultCodesSearchSuccess = [
389
397
  ResultCodeSuccess,
390
398
  ResultCodeTimeLimitExceeded,
391
- ResultCodeSizeLimitExceeded
399
+ ResultCodeSizeLimitExceeded,
392
400
  ]
393
401
 
394
402
  # map of result code to human message
@@ -430,7 +438,7 @@ class Net::LDAP
430
438
  ResultCodeEntryAlreadyExists => "Entry Already Exists",
431
439
  ResultCodeObjectClassModsProhibited => "ObjectClass Modifications Prohibited",
432
440
  ResultCodeAffectsMultipleDSAs => "Affects Multiple DSAs",
433
- ResultCodeOther => "Other"
441
+ ResultCodeOther => "Other",
434
442
  }
435
443
 
436
444
  module LDAPControls
@@ -531,7 +539,7 @@ class Net::LDAP
531
539
  @auth = args[:auth] || DefaultAuth
532
540
  @base = args[:base] || DefaultTreebase
533
541
  @force_no_page = args[:force_no_page] || DefaultForceNoPage
534
- @encryption = args[:encryption] # may be nil
542
+ @encryption = normalize_encryption(args[:encryption]) # may be nil
535
543
  @connect_timeout = args[:connect_timeout]
536
544
 
537
545
  if pr = @auth[:password] and pr.respond_to?(:call)
@@ -583,7 +591,7 @@ class Net::LDAP
583
591
  @auth = {
584
592
  :method => :simple,
585
593
  :username => username,
586
- :password => password
594
+ :password => password,
587
595
  }
588
596
  end
589
597
  alias_method :auth, :authenticate
@@ -601,13 +609,7 @@ class Net::LDAP
601
609
  def encryption(args)
602
610
  warn "Deprecation warning: please give :encryption option as a Hash to Net::LDAP.new"
603
611
  return if args.nil?
604
- return @encryption = args if args.is_a? Hash
605
-
606
- case method = args.to_sym
607
- when :simple_tls, :start_tls
608
- args = { :method => method, :tls_options => {} }
609
- end
610
- @encryption = args
612
+ @encryption = normalize_encryption(args)
611
613
  end
612
614
 
613
615
  # #open takes the same parameters as #new. #open makes a network
@@ -651,8 +653,11 @@ class Net::LDAP
651
653
  #++
652
654
  def get_operation_result
653
655
  result = @result
654
- result = result.result if result.is_a?(Net::LDAP::PDU)
655
656
  os = OpenStruct.new
657
+ if result.is_a?(Net::LDAP::PDU)
658
+ os.extended_response = result.extended_response
659
+ result = result.result
660
+ end
656
661
  if result.is_a?(Hash)
657
662
  # We might get a hash of LDAP response codes instead of a simple
658
663
  # numeric code.
@@ -764,10 +769,10 @@ class Net::LDAP
764
769
 
765
770
  instrument "search.net_ldap", args do |payload|
766
771
  @result = use_connection(args) do |conn|
767
- conn.search(args) { |entry|
772
+ conn.search(args) do |entry|
768
773
  result_set << entry if result_set
769
774
  yield entry if block_given?
770
- }
775
+ end
771
776
  end
772
777
 
773
778
  if return_result_set
@@ -906,7 +911,7 @@ class Net::LDAP
906
911
  # end
907
912
  def bind_as(args = {})
908
913
  result = false
909
- open { |me|
914
+ open do |me|
910
915
  rs = search args
911
916
  if rs and rs.first and dn = rs.first.dn
912
917
  password = args[:password]
@@ -914,7 +919,7 @@ class Net::LDAP
914
919
  result = rs if bind(:method => :simple, :username => dn,
915
920
  :password => password)
916
921
  end
917
- }
922
+ end
918
923
  result
919
924
  end
920
925
 
@@ -1041,6 +1046,44 @@ class Net::LDAP
1041
1046
  end
1042
1047
  end
1043
1048
 
1049
+ # Password Modify
1050
+ #
1051
+ # Change existing password:
1052
+ #
1053
+ # dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com'
1054
+ # auth = {
1055
+ # method: :simple,
1056
+ # username: dn,
1057
+ # password: 'passworD1'
1058
+ # }
1059
+ # ldap.password_modify(dn: dn,
1060
+ # auth: auth,
1061
+ # old_password: 'passworD1',
1062
+ # new_password: 'passworD2')
1063
+ #
1064
+ # Or get the LDAP server to generate a password for you:
1065
+ #
1066
+ # dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com'
1067
+ # auth = {
1068
+ # method: :simple,
1069
+ # username: dn,
1070
+ # password: 'passworD1'
1071
+ # }
1072
+ # ldap.password_modify(dn: dn,
1073
+ # auth: auth,
1074
+ # old_password: 'passworD1')
1075
+ #
1076
+ # ldap.get_operation_result.extended_response[0][0] #=> 'VtcgGf/G'
1077
+ #
1078
+ def password_modify(args)
1079
+ instrument "modify_password.net_ldap", args do |payload|
1080
+ @result = use_connection(args) do |conn|
1081
+ conn.password_modify(args)
1082
+ end
1083
+ @result.success?
1084
+ end
1085
+ end
1086
+
1044
1087
  # Add a value to an attribute. Takes the full DN of the entry to modify,
1045
1088
  # the name (Symbol or String) of the attribute, and the value (String or
1046
1089
  # Array). If the attribute does not exist (and there are no schema
@@ -1159,7 +1202,7 @@ class Net::LDAP
1159
1202
  :supportedExtension,
1160
1203
  :supportedFeatures,
1161
1204
  :supportedLdapVersion,
1162
- :supportedSASLMechanisms
1205
+ :supportedSASLMechanisms,
1163
1206
  ])
1164
1207
  (rs and rs.first) or Net::LDAP::Entry.new
1165
1208
  end
@@ -1226,6 +1269,11 @@ class Net::LDAP
1226
1269
  inspected
1227
1270
  end
1228
1271
 
1272
+ # Internal: Set @open_connection for testing
1273
+ def connection=(connection)
1274
+ @open_connection = connection
1275
+ end
1276
+
1229
1277
  private
1230
1278
 
1231
1279
  # Yields an open connection if there is one, otherwise establishes a new
@@ -1251,18 +1299,35 @@ class Net::LDAP
1251
1299
 
1252
1300
  # Establish a new connection to the LDAP server
1253
1301
  def new_connection
1254
- Net::LDAP::Connection.new \
1302
+ connection = Net::LDAP::Connection.new \
1255
1303
  :host => @host,
1256
1304
  :port => @port,
1257
1305
  :hosts => @hosts,
1258
1306
  :encryption => @encryption,
1259
1307
  :instrumentation_service => @instrumentation_service,
1260
1308
  :connect_timeout => @connect_timeout
1309
+
1310
+ # Force connect to see if there's a connection error
1311
+ connection.socket
1312
+ connection
1261
1313
  rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Net::LDAP::ConnectionRefusedError => e
1262
1314
  @result = {
1263
1315
  :resultCode => 52,
1264
- :errorMessage => ResultStrings[ResultCodeUnavailable]
1316
+ :errorMessage => ResultStrings[ResultCodeUnavailable],
1265
1317
  }
1266
1318
  raise e
1267
1319
  end
1320
+
1321
+ # Normalize encryption parameter the constructor accepts, expands a few
1322
+ # convenience symbols into recognizable hashes
1323
+ def normalize_encryption(args)
1324
+ return if args.nil?
1325
+ return args if args.is_a? Hash
1326
+
1327
+ case method = args.to_sym
1328
+ when :simple_tls, :start_tls
1329
+ { :method => method, :tls_options => {} }
1330
+ end
1331
+ end
1332
+
1268
1333
  end # class LDAP
@@ -22,12 +22,12 @@ module Net
22
22
  user, psw = [auth[:username] || auth[:dn], auth[:password]]
23
23
  raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw)
24
24
 
25
- nego = proc { |challenge|
25
+ nego = proc do |challenge|
26
26
  t2_msg = NTLM::Message.parse(challenge)
27
27
  t3_msg = t2_msg.response({ :user => user, :password => psw },
28
28
  { :ntlmv2 => true })
29
29
  t3_msg.serialize
30
- }
30
+ end
31
31
 
32
32
  Net::LDAP::AuthAdapter::Sasl.new(@connection).bind \
33
33
  :method => :sasl,
@@ -33,7 +33,7 @@ module Net
33
33
  message_id = @connection.next_msgid
34
34
 
35
35
  n = 0
36
- loop {
36
+ loop do
37
37
  sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3)
38
38
  request = [
39
39
  Net::LDAP::Connection::LdapVersion.to_ber, "".to_ber, sasl
@@ -50,7 +50,7 @@ module Net
50
50
  raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MaxSaslChallenges)
51
51
 
52
52
  cred = chall.call(pdu.result_server_sasl_creds)
53
- }
53
+ end
54
54
 
55
55
  raise Net::LDAP::SASLChallengeOverflowError, "why are we here?"
56
56
  end
@@ -9,19 +9,28 @@ class Net::LDAP::Connection #:nodoc:
9
9
  LdapVersion = 3
10
10
  MaxSaslChallenges = 10
11
11
 
12
- def initialize(server)
12
+ # Initialize a connection to an LDAP server
13
+ #
14
+ # :server
15
+ # :hosts Array of tuples specifying host, port
16
+ # :host host
17
+ # :port port
18
+ # :socket prepared socket
19
+ #
20
+ def initialize(server = {})
21
+ @server = server
13
22
  @instrumentation_service = server[:instrumentation_service]
14
23
 
15
- if server[:socket]
16
- prepare_socket(server)
17
- else
18
- server[:hosts] = [[server[:host], server[:port]]] if server[:hosts].nil?
19
- open_connection(server)
20
- end
24
+ # Allows tests to parameterize what socket class to use
25
+ @socket_class = server.fetch(:socket_class, DefaultSocket)
21
26
 
22
27
  yield self if block_given?
23
28
  end
24
29
 
30
+ def socket_class=(socket_class)
31
+ @socket_class = socket_class
32
+ end
33
+
25
34
  def prepare_socket(server)
26
35
  socket = server[:socket]
27
36
  encryption = server[:encryption]
@@ -35,13 +44,13 @@ class Net::LDAP::Connection #:nodoc:
35
44
  encryption = server[:encryption]
36
45
 
37
46
  socket_opts = {
38
- connect_timeout: server[:connect_timeout] || DefaultConnectTimeout
47
+ connect_timeout: server[:connect_timeout] || DefaultConnectTimeout,
39
48
  }
40
49
 
41
50
  errors = []
42
51
  hosts.each do |host, port|
43
52
  begin
44
- prepare_socket(server.merge(socket: Socket.tcp(host, port, socket_opts)))
53
+ prepare_socket(server.merge(socket: @socket_class.new(host, port, socket_opts)))
45
54
  return
46
55
  rescue Net::LDAP::Error, SocketError, SystemCallError,
47
56
  OpenSSL::SSL::SSLError => e
@@ -124,7 +133,7 @@ class Net::LDAP::Connection #:nodoc:
124
133
  when :start_tls
125
134
  message_id = next_msgid
126
135
  request = [
127
- Net::LDAP::StartTlsOid.to_ber_contextspecific(0)
136
+ Net::LDAP::StartTlsOid.to_ber_contextspecific(0),
128
137
  ].to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
129
138
 
130
139
  write(request, nil, message_id)
@@ -202,7 +211,7 @@ class Net::LDAP::Connection #:nodoc:
202
211
  def read(syntax = Net::LDAP::AsnSyntax)
203
212
  ber_object =
204
213
  instrument "read.net_ldap_connection", :syntax => syntax do |payload|
205
- @conn.read_ber(syntax) do |id, content_length|
214
+ socket.read_ber(syntax) do |id, content_length|
206
215
  payload[:object_type_id] = id
207
216
  payload[:content_length] = content_length
208
217
  end
@@ -232,7 +241,7 @@ class Net::LDAP::Connection #:nodoc:
232
241
  def write(request, controls = nil, message_id = next_msgid)
233
242
  instrument "write.net_ldap_connection" do |payload|
234
243
  packet = [message_id.to_ber, request, controls].compact.to_ber_sequence
235
- payload[:content_length] = @conn.write(packet)
244
+ payload[:content_length] = socket.write(packet)
236
245
  end
237
246
  end
238
247
  private :write
@@ -274,7 +283,7 @@ class Net::LDAP::Connection #:nodoc:
274
283
  sort_control = [
275
284
  Net::LDAP::LDAPControls::SORT_REQUEST.to_ber,
276
285
  false.to_ber,
277
- sort_control_values.to_ber_sequence.to_s.to_ber
286
+ sort_control_values.to_ber_sequence.to_s.to_ber,
278
287
  ].to_ber_sequence
279
288
  end
280
289
 
@@ -387,7 +396,7 @@ class Net::LDAP::Connection #:nodoc:
387
396
  time.to_ber,
388
397
  attrs_only.to_ber,
389
398
  filter.to_ber,
390
- ber_attrs.to_ber_sequence
399
+ ber_attrs.to_ber_sequence,
391
400
  ].to_ber_appsequence(Net::LDAP::PDU::SearchRequest)
392
401
 
393
402
  # rfc2696_cookie sometimes contains binary data from Microsoft Active Directory
@@ -400,7 +409,7 @@ class Net::LDAP::Connection #:nodoc:
400
409
  Net::LDAP::LDAPControls::PAGED_RESULTS.to_ber,
401
410
  # Criticality MUST be false to interoperate with normal LDAPs.
402
411
  false.to_ber,
403
- rfc2696_cookie.map{ |v| v.to_ber}.to_ber_sequence.to_s.to_ber
412
+ rfc2696_cookie.map(&:to_ber).to_ber_sequence.to_s.to_ber,
404
413
  ].to_ber_sequence if paged
405
414
  controls << ber_sort if ber_sort
406
415
  controls = controls.empty? ? nil : controls.to_ber_contextspecific(0)
@@ -494,20 +503,20 @@ class Net::LDAP::Connection #:nodoc:
494
503
  MODIFY_OPERATIONS = { #:nodoc:
495
504
  :add => 0,
496
505
  :delete => 1,
497
- :replace => 2
506
+ :replace => 2,
498
507
  }
499
508
 
500
509
  def self.modify_ops(operations)
501
510
  ops = []
502
511
  if operations
503
- operations.each { |op, attrib, values|
512
+ operations.each do |op, attrib, values|
504
513
  # TODO, fix the following line, which gives a bogus error if the
505
514
  # opcode is invalid.
506
515
  op_ber = MODIFY_OPERATIONS[op.to_sym].to_ber_enumerated
507
- values = [ values ].flatten.map { |v| v.to_ber if v }.to_ber_set
508
- values = [ attrib.to_s.to_ber, values ].to_ber_sequence
509
- ops << [ op_ber, values ].to_ber
510
- }
516
+ values = [values].flatten.map { |v| v.to_ber if v }.to_ber_set
517
+ values = [attrib.to_s.to_ber, values].to_ber_sequence
518
+ ops << [op_ber, values].to_ber
519
+ end
511
520
  end
512
521
  ops
513
522
  end
@@ -526,7 +535,7 @@ class Net::LDAP::Connection #:nodoc:
526
535
  message_id = next_msgid
527
536
  request = [
528
537
  modify_dn.to_ber,
529
- ops.to_ber_sequence
538
+ ops.to_ber_sequence,
530
539
  ].to_ber_appsequence(Net::LDAP::PDU::ModifyRequest)
531
540
 
532
541
  write(request, nil, message_id)
@@ -539,6 +548,51 @@ class Net::LDAP::Connection #:nodoc:
539
548
  pdu
540
549
  end
541
550
 
551
+ ##
552
+ # Password Modify
553
+ #
554
+ # http://tools.ietf.org/html/rfc3062
555
+ #
556
+ # passwdModifyOID OBJECT IDENTIFIER ::= 1.3.6.1.4.1.4203.1.11.1
557
+ #
558
+ # PasswdModifyRequestValue ::= SEQUENCE {
559
+ # userIdentity [0] OCTET STRING OPTIONAL
560
+ # oldPasswd [1] OCTET STRING OPTIONAL
561
+ # newPasswd [2] OCTET STRING OPTIONAL }
562
+ #
563
+ # PasswdModifyResponseValue ::= SEQUENCE {
564
+ # genPasswd [0] OCTET STRING OPTIONAL }
565
+ #
566
+ # Encoded request:
567
+ #
568
+ # 00\x02\x01\x02w+\x80\x171.3.6.1.4.1.4203.1.11.1\x81\x100\x0E\x81\x05old\x82\x05new
569
+ #
570
+ def password_modify(args)
571
+ dn = args[:dn]
572
+ raise ArgumentError, 'DN is required' if !dn || dn.empty?
573
+
574
+ ext_seq = [Net::LDAP::PasswdModifyOid.to_ber_contextspecific(0)]
575
+
576
+ unless args[:old_password].nil?
577
+ pwd_seq = [args[:old_password].to_ber(0x81)]
578
+ pwd_seq << args[:new_password].to_ber(0x82) unless args[:new_password].nil?
579
+ ext_seq << pwd_seq.to_ber_sequence.to_ber(0x81)
580
+ end
581
+
582
+ request = ext_seq.to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)
583
+
584
+ message_id = next_msgid
585
+
586
+ write(request, nil, message_id)
587
+ pdu = queued_read(message_id)
588
+
589
+ if !pdu || pdu.app_tag != Net::LDAP::PDU::ExtendedResponse
590
+ raise Net::LDAP::ResponseMissingError, "response missing or invalid"
591
+ end
592
+
593
+ pdu
594
+ end
595
+
542
596
  #--
543
597
  # TODO: need to support a time limit, in case the server fails to respond.
544
598
  # Unlike other operation-methods in this class, we return a result hash
@@ -549,9 +603,9 @@ class Net::LDAP::Connection #:nodoc:
549
603
  def add(args)
550
604
  add_dn = args[:dn] or raise Net::LDAP::EmptyDNError, "Unable to add empty DN"
551
605
  add_attrs = []
552
- a = args[:attributes] and a.each { |k, v|
553
- add_attrs << [ k.to_s.to_ber, Array(v).map { |m| m.to_ber}.to_ber_set ].to_ber_sequence
554
- }
606
+ a = args[:attributes] and a.each do |k, v|
607
+ add_attrs << [k.to_s.to_ber, Array(v).map(&:to_ber).to_ber_set].to_ber_sequence
608
+ end
555
609
 
556
610
  message_id = next_msgid
557
611
  request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(Net::LDAP::PDU::AddRequest)
@@ -607,4 +661,33 @@ class Net::LDAP::Connection #:nodoc:
607
661
 
608
662
  pdu
609
663
  end
664
+
665
+ # Internal: Returns a Socket like object used internally to communicate with
666
+ # LDAP server.
667
+ #
668
+ # Typically a TCPSocket, but can be a OpenSSL::SSL::SSLSocket
669
+ def socket
670
+ return @conn if defined? @conn
671
+
672
+ # First refactoring uses the existing methods open_connection and
673
+ # prepare_socket to set @conn. Next cleanup would centralize connection
674
+ # handling here.
675
+ if @server[:socket]
676
+ prepare_socket(@server)
677
+ else
678
+ @server[:hosts] = [[@server[:host], @server[:port]]] if @server[:hosts].nil?
679
+ open_connection(@server)
680
+ end
681
+
682
+ @conn
683
+ end
684
+
685
+ private
686
+
687
+ # Wrap around Socket.tcp to normalize with other Socket initializers
688
+ class DefaultSocket
689
+ def self.new(host, port, socket_opts = {})
690
+ Socket.tcp(host, port, socket_opts)
691
+ end
692
+ end
610
693
  end # class Connection