mongo 1.11.1 → 1.12.0.rc0

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 (39) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/VERSION +1 -1
  5. data/lib/mongo/collection.rb +8 -3
  6. data/lib/mongo/collection_writer.rb +1 -1
  7. data/lib/mongo/connection/socket/unix_socket.rb +1 -1
  8. data/lib/mongo/cursor.rb +8 -1
  9. data/lib/mongo/db.rb +61 -33
  10. data/lib/mongo/exception.rb +57 -0
  11. data/lib/mongo/functional.rb +1 -0
  12. data/lib/mongo/functional/authentication.rb +138 -11
  13. data/lib/mongo/functional/read_preference.rb +31 -22
  14. data/lib/mongo/functional/scram.rb +555 -0
  15. data/lib/mongo/functional/uri_parser.rb +107 -79
  16. data/lib/mongo/mongo_client.rb +19 -24
  17. data/lib/mongo/mongo_replica_set_client.rb +2 -1
  18. data/test/functional/authentication_test.rb +3 -0
  19. data/test/functional/client_test.rb +33 -0
  20. data/test/functional/collection_test.rb +29 -19
  21. data/test/functional/db_api_test.rb +16 -1
  22. data/test/functional/pool_test.rb +7 -6
  23. data/test/functional/uri_test.rb +111 -7
  24. data/test/helpers/test_unit.rb +17 -3
  25. data/test/replica_set/client_test.rb +31 -0
  26. data/test/replica_set/insert_test.rb +49 -32
  27. data/test/replica_set/pinning_test.rb +50 -0
  28. data/test/replica_set/query_test.rb +1 -1
  29. data/test/replica_set/replication_ack_test.rb +3 -3
  30. data/test/shared/authentication/basic_auth_shared.rb +14 -1
  31. data/test/shared/authentication/gssapi_shared.rb +13 -8
  32. data/test/shared/authentication/scram_shared.rb +92 -0
  33. data/test/tools/mongo_config.rb +18 -6
  34. data/test/unit/client_test.rb +40 -6
  35. data/test/unit/connection_test.rb +15 -5
  36. data/test/unit/db_test.rb +1 -1
  37. data/test/unit/read_pref_test.rb +291 -0
  38. metadata +9 -6
  39. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 047647bfd88def86157a8e4e25df514da21a1364
4
- data.tar.gz: 7fd48a7694323f7e4759b94f12058b5e34a6bba4
3
+ metadata.gz: 0c376366d59154151291d12a0a859a6db8850416
4
+ data.tar.gz: 7d92d814592073a3f2548fea9b2960e241c98502
5
5
  SHA512:
6
- metadata.gz: 3b360776478001d2b41342764c13dd209954c0bbcf73c5b87dc4f2169d3e42bd27c6650514ca281a1146c33d1d2624edf653cf8ddd51e3ff8f9e37c9bf4505d3
7
- data.tar.gz: e4a9d033eb058e0424ecae35599a27be0a7c68e56b2a0557dcbc42a331e940a0c42c73f0b1c1eeb3b76d977fe63a84df5b7310bc001feeb2f8441518e163b2eb
6
+ metadata.gz: dc0c717e7741c0871d80af3b6f65be1bd8908a2e0364c6bf17106056f5d71ea4677f410a4cacf0a6ec85181486a2e3eb5392a1b09472c0d728ac9c3dcaecd027
7
+ data.tar.gz: fd7c88744f849a3cdec1a1e514a2028d4391615028dceb3c48edb0fbe5b6f87a1bceae1745cbc96ad2c0f96f0380897d6f1a9668d4a620adb9d52b0b32c67fe5
Binary file
data.tar.gz.sig CHANGED
Binary file
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.11.1
1
+ 1.12.0.rc0
@@ -717,11 +717,13 @@ module Mongo
717
717
 
718
718
  if result.key?('cursor')
719
719
  cursor_info = result['cursor']
720
+ pinned_pool = @connection.pinned_pool
721
+ pinned_pool = pinned_pool[:pool] if pinned_pool.respond_to?(:keys)
720
722
 
721
723
  seed = {
722
724
  :cursor_id => cursor_info['id'],
723
725
  :first_batch => cursor_info['firstBatch'],
724
- :pool => @connection.pinned_pool
726
+ :pool => pinned_pool
725
727
  }
726
728
 
727
729
  return Cursor.new(self, seed.merge!(opts))
@@ -887,10 +889,13 @@ module Mongo
887
889
  result = @db.command(cmd, command_options(opts))
888
890
 
889
891
  result['cursors'].collect do |cursor_info|
892
+ pinned_pool = @connection.pinned_pool
893
+ pinned_pool = pinned_pool[:pool] if pinned_pool.respond_to?(:keys)
894
+
890
895
  seed = {
891
896
  :cursor_id => cursor_info['cursor']['id'],
892
897
  :first_batch => cursor_info['cursor']['firstBatch'],
893
- :pool => @connection.pinned_pool
898
+ :pool => pinned_pool
894
899
  }
895
900
  Cursor.new(self, seed.merge!(opts))
896
901
  end
@@ -1024,7 +1029,7 @@ module Mongo
1024
1029
  #
1025
1030
  # @return [Hash] options that apply to this collection.
1026
1031
  def options
1027
- @db.collections_info(@name).next_document['options']
1032
+ @db.collections_info(@name).first['options']
1028
1033
  end
1029
1034
 
1030
1035
  # Return stats on the collection. Uses MongoDB's collstats command.
@@ -55,7 +55,7 @@ module Mongo
55
55
  @max_write_batch_size = @collection.db.connection.max_write_batch_size
56
56
  docs = documents.dup
57
57
  catch(:error) do
58
- until docs.empty? || (!errors.empty? && !collect_on_error) # process documents a batch at a time
58
+ until docs.empty? || (!errors.empty? && !collect_on_error && !continue_on_error) # process documents a batch at a time
59
59
  batch_docs = []
60
60
  batch_message_initialize(message, op_type, continue_on_error, write_concern)
61
61
  while !docs.empty? && batch_docs.size < @max_write_batch_size
@@ -33,7 +33,7 @@ module Mongo
33
33
 
34
34
  @socket_address = Socket.pack_sockaddr_un(@address)
35
35
  @socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
36
- connect
36
+ connect(@socket, @socket_address)
37
37
  end
38
38
  end
39
39
  end
@@ -564,9 +564,11 @@ module Mongo
564
564
  ensure
565
565
  socket.checkin unless @socket || socket.nil?
566
566
  end
567
- if !@socket && !@command
567
+
568
+ if pin_pool?(results.first)
568
569
  @connection.pin_pool(socket.pool, read_preference)
569
570
  end
571
+
570
572
  @returned += @n_received
571
573
  @cache += results
572
574
  @query_run = true
@@ -728,5 +730,10 @@ module Mongo
728
730
  end
729
731
  indexes.join("_")
730
732
  end
733
+
734
+ def pin_pool?(response)
735
+ ( response && (response['cursor'] || response['cursors']) ) ||
736
+ ( !@socket && !@command )
737
+ end
731
738
  end
732
739
  end
@@ -217,27 +217,25 @@ module Mongo
217
217
  #
218
218
  # @return [Hash] an object representing the user.
219
219
  def add_user(username, password=nil, read_only=false, opts={})
220
- begin
221
- user_info = command(:usersInfo => username)
222
- if user_info.key?('users') && !user_info['users'].empty?
223
- create_or_update_user(:updateUser, username, password, read_only, opts)
224
- else
225
- create_or_update_user(:createUser, username, password, read_only, opts)
226
- end
227
- # MongoDB >= 2.5.3 requires the use of commands to manage users.
228
- # "Command not found" error didn't return an error code (59) before
229
- # MongoDB 2.4.7 so we assume that a nil error code means the usersInfo
230
- # command doesn't exist and we should fall back to the legacy add user code.
231
- rescue OperationFailure => ex
232
- if Mongo::ErrorCode::COMMAND_NOT_FOUND_CODES.include?(ex.error_code)
233
- legacy_add_user(username, password, read_only, opts)
234
- elsif ex.error_code == Mongo::ErrorCode::UNAUTHORIZED
235
- # In MongoDB > 2.7 the localhost exception was narrowed, and the usersInfo
236
- # command is no longer allowed. In this case, add the first user.
237
- create_or_update_user(:createUser, username, password, read_only, opts)
238
- else
239
- raise ex
240
- end
220
+ user_info = command(:usersInfo => username)
221
+ if user_info.key?('users') && !user_info['users'].empty?
222
+ create_or_update_user(:updateUser, username, password, read_only, opts)
223
+ else
224
+ create_or_update_user(:createUser, username, password, read_only, opts)
225
+ end
226
+ # MongoDB >= 2.5.3 requires the use of commands to manage users.
227
+ # "Command not found" error didn't return an error code (59) before
228
+ # MongoDB 2.4.7 so we assume that a nil error code means the usersInfo
229
+ # command doesn't exist and we should fall back to the legacy add user code.
230
+ rescue OperationFailure => ex
231
+ if Mongo::ErrorCode::COMMAND_NOT_FOUND_CODES.include?(ex.error_code)
232
+ legacy_add_user(username, password, read_only, opts)
233
+ elsif ex.error_code == Mongo::ErrorCode::UNAUTHORIZED
234
+ # In MongoDB > 2.7 the localhost exception was narrowed, and the usersInfo
235
+ # command is no longer allowed. In this case, add the first user.
236
+ create_or_update_user(:createUser, username, password, read_only, opts)
237
+ else
238
+ raise ex
241
239
  end
242
240
  end
243
241
 
@@ -261,9 +259,14 @@ module Mongo
261
259
  #
262
260
  # @return [Array]
263
261
  def collection_names
264
- names = collections_info.collect { |doc| doc['name'] || '' }
265
- names = names.delete_if {|name| name.index(@name).nil? || name.index('$')}
266
- names.map {|name| name.sub(@name + '.', '')}
262
+ if @client.wire_version_feature?(Mongo::MongoClient::MONGODB_2_8)
263
+ names = collections_info.collect { |doc| doc['name'] || '' }
264
+ names.delete_if do |name|
265
+ name.index('$')
266
+ end
267
+ else
268
+ legacy_collection_names
269
+ end
267
270
  end
268
271
 
269
272
  # Get an array of Collection instances, one for each collection in this database.
@@ -281,11 +284,15 @@ module Mongo
281
284
  #
282
285
  # @param [String] coll_name return info for the specified collection only.
283
286
  #
284
- # @return [Mongo::Cursor]
287
+ # @return [Array] List of collection info.
285
288
  def collections_info(coll_name=nil)
286
- selector = {}
287
- selector[:name] = full_collection_name(coll_name) if coll_name
288
- Cursor.new(Collection.new(SYSTEM_NAMESPACE_COLLECTION, self), :selector => selector)
289
+ if @client.wire_version_feature?(Mongo::MongoClient::MONGODB_2_8)
290
+ cmd = BSON::OrderedHash[:listCollections, 1]
291
+ cmd.merge!(:filter => { :name => coll_name }) if coll_name
292
+ self.command(cmd)['collections']
293
+ else
294
+ legacy_collections_info(coll_name).to_a
295
+ end
289
296
  end
290
297
 
291
298
  # Create a collection.
@@ -482,12 +489,14 @@ module Mongo
482
489
  # @return [Hash] keys are index names and the values are lists of [key, type] pairs
483
490
  # defining the index.
484
491
  def index_information(collection_name)
485
- sel = {:ns => full_collection_name(collection_name)}
486
- info = {}
487
- Cursor.new(Collection.new(SYSTEM_INDEX_COLLECTION, self), :selector => sel).each do |index|
488
- info[index['name']] = index
492
+ if @client.wire_version_feature?(Mongo::MongoClient::MONGODB_2_8)
493
+ result = self.command(:listIndexes => collection_name)['indexes']
494
+ else
495
+ result = legacy_list_indexes(collection_name)
496
+ end
497
+ result.reduce({}) do |info, index|
498
+ info.merge!(index['name'] => index)
489
499
  end
490
- info
491
500
  end
492
501
 
493
502
  # Return stats on this database. Uses MongoDB's dbstats command.
@@ -736,5 +745,24 @@ module Mongo
736
745
  end
737
746
  user
738
747
  end
748
+
749
+ def legacy_list_indexes(collection_name)
750
+ sel = {:ns => full_collection_name(collection_name)}
751
+ Cursor.new(Collection.new(SYSTEM_INDEX_COLLECTION, self), :selector => sel)
752
+ end
753
+
754
+ def legacy_collections_info(coll_name=nil)
755
+ selector = {}
756
+ selector[:name] = full_collection_name(coll_name) if coll_name
757
+ Cursor.new(Collection.new(SYSTEM_NAMESPACE_COLLECTION, self), :selector => selector)
758
+ end
759
+
760
+ def legacy_collection_names
761
+ names = legacy_collections_info.collect { |doc| doc['name'] || '' }
762
+ names = names.delete_if do |name|
763
+ name.index(@name).nil? || name.index('$')
764
+ end
765
+ names.map {|name| name.sub(@name + '.', '')}
766
+ end
739
767
  end
740
768
  end
@@ -85,4 +85,61 @@ module Mongo
85
85
 
86
86
  # Raised for bulk write errors.
87
87
  class BulkWriteError < OperationFailure; end
88
+
89
+ # This exception is raised when the server nonce returned does not
90
+ # match the client nonce sent to it.
91
+ #
92
+ # @since 1.12.0
93
+ class InvalidNonce < OperationFailure
94
+
95
+ # @return [ String ] nonce The client nonce.
96
+ attr_reader :nonce
97
+
98
+ # @return [ String ] rnonce The server nonce.
99
+ attr_reader :rnonce
100
+
101
+ # Instantiate the new exception.
102
+ #
103
+ # @example Create the exception.
104
+ # InvalidNonce.new(nonce, rnonce)
105
+ #
106
+ # @param [ String ] nonce The client nonce.
107
+ # @param [ String ] rnonce The server nonce.
108
+ #
109
+ # @since 1.12.0
110
+ def initialize(nonce, rnonce)
111
+ @nonce = nonce
112
+ @rnonce = rnonce
113
+ super("Expected server rnonce '#{rnonce}' to start with client nonce '#{nonce}'.")
114
+ end
115
+ end
116
+
117
+ # This exception is raised when the server verifier does not match the
118
+ # expected signature on the client.
119
+ #
120
+ # @since 1.12.0
121
+ class InvalidSignature < OperationFailure
122
+
123
+ # @return [ String ] verifier The server verifier string.
124
+ attr_reader :verifier
125
+
126
+ # @return [ String ] server_signature The expected server signature.
127
+ attr_reader :server_signature
128
+
129
+ # Create the new exception.
130
+ #
131
+ # @example Create the new exception.
132
+ # InvalidSignature.new(verifier, server_signature)
133
+ #
134
+ # @param [ String ] verifier The verifier returned from the server.
135
+ # @param [ String ] server_signature The expected value from the
136
+ # server.
137
+ #
138
+ # @since 1.12.0
139
+ def initialize(verifier, server_signature)
140
+ @verifier = verifier
141
+ @server_signature = server_signature
142
+ super("Expected server verifier '#{verifier}' to match '#{server_signature}'.")
143
+ end
144
+ end
88
145
  end
@@ -17,3 +17,4 @@ require 'mongo/functional/logging'
17
17
  require 'mongo/functional/read_preference'
18
18
  require 'mongo/functional/write_concern'
19
19
  require 'mongo/functional/uri_parser'
20
+ require 'mongo/functional/scram'
@@ -18,8 +18,11 @@ module Mongo
18
18
  module Authentication
19
19
 
20
20
  DEFAULT_MECHANISM = 'MONGODB-CR'
21
- MECHANISMS = ['GSSAPI', 'MONGODB-CR', 'MONGODB-X509', 'PLAIN']
22
- EXTRA = { 'GSSAPI' => [:gssapi_service_name, :canonicalize_host_name] }
21
+ MECHANISMS = ['GSSAPI', 'MONGODB-CR', 'MONGODB-X509', 'PLAIN', 'SCRAM-SHA-1']
22
+ MECHANISM_ERROR = "Must use one of #{MECHANISMS.join(', ')} " +
23
+ "authentication mechanisms."
24
+ EXTRA = { 'GSSAPI' => [:service_name, :canonicalize_host_name,
25
+ :service_realm] }
23
26
 
24
27
  # authentication module methods
25
28
  class << self
@@ -49,15 +52,13 @@ module Mongo
49
52
  # @raise [MongoArgumentError] if the credential set is invalid.
50
53
  # @return [Hash] The validated credential set.
51
54
  def validate_credentials(auth)
52
- # set the default auth mechanism if not defined
53
- auth[:mechanism] ||= DEFAULT_MECHANISM
54
-
55
55
  # set the default auth source if not defined
56
56
  auth[:source] = auth[:source] || auth[:db_name] || 'admin'
57
57
 
58
- if (auth[:mechanism] == 'MONGODB-CR' || auth[:mechanism] == 'PLAIN') && !auth[:password]
58
+ if password_required?(auth[:mechanism]) && !auth[:password]
59
59
  raise MongoArgumentError,
60
- "When using the authentication mechanism #{auth[:mechanism]} " +
60
+ "When using the authentication mechanism " +
61
+ "#{auth[:mechanism].nil? ? 'MONGODB-CR or SCRAM-SHA-1' : auth[:mechanism]} " +
61
62
  "both username and password are required."
62
63
  end
63
64
  # if extra opts exist, validate them
@@ -92,6 +93,19 @@ module Mongo
92
93
  def hash_password(username, password)
93
94
  Digest::MD5.hexdigest("#{username}:mongo:#{password}")
94
95
  end
96
+
97
+ private
98
+
99
+ # Does the authentication require a password?
100
+ #
101
+ # @param [ String ] mech The authentication mechanism.
102
+ #
103
+ # @return [ true, false ] If a password is required.
104
+ #
105
+ # @since 1.12.0
106
+ def password_required?(mech)
107
+ mech == 'MONGODB-CR' || mech == 'PLAIN' || mech == 'SCRAM-SHA-1' || mech.nil?
108
+ end
95
109
  end
96
110
 
97
111
  # Saves a cache of authentication credentials to the current
@@ -104,7 +118,7 @@ module Mongo
104
118
  # @param source [String] (nil) The authentication source database
105
119
  # (if different than the current database).
106
120
  # @param mechanism [String] (nil) The authentication mechanism being used
107
- # (default: 'MONGODB-CR').
121
+ # (default: 'MONGODB-CR' or 'SCRAM-SHA-1' if server version >= 2.7.8).
108
122
  # @param extra [Hash] (nil) A optional hash of extra options to be stored with
109
123
  # the credential set.
110
124
  #
@@ -131,8 +145,8 @@ module Mongo
131
145
  end
132
146
 
133
147
  begin
134
- socket = self.checkout_reader(:mode => :primary_preferred)
135
- self.issue_authentication(auth, :socket => socket)
148
+ socket = checkout_reader(:mode => :primary_preferred)
149
+ issue_authentication(auth, :socket => socket)
136
150
  ensure
137
151
  socket.checkin if socket
138
152
  end
@@ -148,7 +162,10 @@ module Mongo
148
162
  # @return [Boolean] The result of the operation.
149
163
  def remove_auth(db_name)
150
164
  return false unless @auths
151
- @auths.reject! { |a| a[:source] == db_name } ? true : false
165
+ auths = @auths.to_a
166
+ removed = auths.reject! { |a| a[:source] == db_name }
167
+ @auths = Set.new(auths)
168
+ !!removed
152
169
  end
153
170
 
154
171
  # Remove all authentication information stored in this connection.
@@ -190,6 +207,11 @@ module Mongo
190
207
  # @raise [AuthenticationError] Raised if the authentication fails.
191
208
  # @return [Boolean] Result of the authentication operation.
192
209
  def issue_authentication(auth, opts={})
210
+ # set the default auth mechanism if not defined
211
+ auth[:mechanism] ||= default_mechanism
212
+
213
+ raise MongoArgumentError,
214
+ MECHANISM_ERROR unless MECHANISMS.include?(auth[:mechanism])
193
215
  result = case auth[:mechanism]
194
216
  when 'MONGODB-CR'
195
217
  issue_cr(auth, opts)
@@ -199,6 +221,8 @@ module Mongo
199
221
  issue_plain(auth, opts)
200
222
  when 'GSSAPI'
201
223
  issue_gssapi(auth, opts)
224
+ when 'SCRAM-SHA-1'
225
+ issue_scram(auth, opts)
202
226
  end
203
227
 
204
228
  unless Support.ok?(result)
@@ -212,6 +236,86 @@ module Mongo
212
236
 
213
237
  private
214
238
 
239
+ def default_mechanism
240
+ max_wire_version >= 3 ? 'SCRAM-SHA-1' : DEFAULT_MECHANISM
241
+ end
242
+
243
+ # Handles copying a database with SCRAM-SHA-1 authentication.
244
+ #
245
+ # @api private
246
+ #
247
+ # @param [ String ] username The user to authenticate on the
248
+ # 'from' database.
249
+ # @param [ String ] password The password for the user authenticated
250
+ # on the 'from' database.
251
+ # @param [ String ] from_host The host of the 'from' database.
252
+ # @param [ String ] from_db Name of the database to copy from.
253
+ # @param [ String ] to_db Name of the database to copy to.
254
+ #
255
+ # @return [ Hash ] The result of the copydb operation.
256
+ #
257
+ # @since 1.12.0
258
+ def copy_db_scram(username, password, from_host, from_db, to_db)
259
+ auth = { :db_name => from_db,
260
+ :username => username,
261
+ :password => password }
262
+
263
+ socket = checkout_reader(:mode => :primary_preferred)
264
+
265
+ copy_db = { :from_host => from_host, :from_db => from_db, :to_db => to_db }
266
+ scram = SCRAM.new(auth, Authentication.hash_password(username, password),
267
+ { :copy_db => copy_db })
268
+ result = auth_command(scram.copy_db_start, socket, 'admin').first
269
+ result = auth_command(scram.copy_db_continue(result), socket, 'admin').first
270
+ until result['done']
271
+ result = auth_command(scram.copy_db_continue(result), socket, 'admin').first
272
+ end
273
+ socket.checkin
274
+ result
275
+ end
276
+
277
+ # Handles copying a database with MONGODB-CR authentication.
278
+ #
279
+ # @api private
280
+ #
281
+ # @param [ String ] username The user to authenticate on the
282
+ # 'from' database.
283
+ # @param [ String ] password The password for the user authenticated
284
+ # on the 'from' database.
285
+ # @param [ String ] from_host The host of the 'from' database.
286
+ # @param [ String ] from_db Name of the database to copy from.
287
+ # @param [ String ] to_db Name of the database to copy to.
288
+ #
289
+ # @return [ Hash ] The result of the copydb operation.
290
+ #
291
+ # @since 1.12.0
292
+ def copy_db_mongodb_cr(username, password, from_host, from_db, to_db)
293
+ oh = BSON::OrderedHash.new
294
+ oh[:copydb] = 1
295
+ oh[:fromhost] = from_host
296
+ oh[:fromdb] = from_db
297
+ oh[:todb] = to_db
298
+
299
+ socket = checkout_reader(:mode => :primary_preferred)
300
+
301
+ if username || password
302
+ unless username && password
303
+ raise MongoArgumentError,
304
+ 'Both username and password must be supplied for authentication.'
305
+ end
306
+ nonce_cmd = BSON::OrderedHash.new
307
+ nonce_cmd[:copydbgetnonce] = 1
308
+ nonce_cmd[:fromhost] = from_host
309
+ result = auth_command(nonce_cmd, socket, 'admin').first
310
+ oh[:nonce] = result['nonce']
311
+ oh[:username] = username
312
+ oh[:key] = Authentication.auth_key(username, password, oh[:nonce])
313
+ end
314
+ result = auth_command(oh, socket, 'admin').first
315
+ socket.checkin
316
+ result
317
+ end
318
+
215
319
  # Handles issuing authentication commands for the MONGODB-CR auth mechanism.
216
320
  #
217
321
  # @param auth [Hash] The authentication credentials to be used.
@@ -287,6 +391,29 @@ module Mongo
287
391
  raise "In order to use Kerberos, please add the mongo-kerberos gem to your dependencies"
288
392
  end
289
393
 
394
+ # Handles issuing SCRAM-SHA-1 authentication.
395
+ #
396
+ # @api private
397
+ #
398
+ # @param [ Hash ] auth The authentication credentials.
399
+ # @param [ Hash ] opts The options.
400
+ #
401
+ # @options opts [ Socket ] socket The Socket instance to use.
402
+ #
403
+ # @return [ Hash ] The result of the authentication operation.
404
+ #
405
+ # @since 1.12.0
406
+ def issue_scram(auth, opts = {})
407
+ db_name = auth[:source]
408
+ scram = SCRAM.new(auth, Authentication.hash_password(auth[:username], auth[:password]))
409
+ result = auth_command(scram.start, opts[:socket], db_name).first
410
+ result = auth_command(scram.continue(result), opts[:socket], db_name).first
411
+ until result['done']
412
+ result = auth_command(scram.finalize(result), opts[:socket], db_name).first
413
+ end
414
+ result
415
+ end
416
+
290
417
  # Helper to fetch a nonce value from a given database instance.
291
418
  #
292
419
  # @param database [Mongo::DB] The DB instance to use for issue the nonce command.