mongo 1.9.2 → 1.10.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +1 -1
  5. data/README.md +94 -334
  6. data/Rakefile +6 -4
  7. data/VERSION +1 -1
  8. data/bin/mongo_console +13 -6
  9. data/lib/mongo.rb +22 -27
  10. data/lib/mongo/bulk_write_collection_view.rb +352 -0
  11. data/lib/mongo/collection.rb +128 -188
  12. data/lib/mongo/collection_writer.rb +348 -0
  13. data/lib/mongo/connection.rb +19 -0
  14. data/lib/mongo/{util → connection}/node.rb +15 -1
  15. data/lib/mongo/{util → connection}/pool.rb +34 -19
  16. data/lib/mongo/{util → connection}/pool_manager.rb +8 -2
  17. data/lib/mongo/{util → connection}/sharding_pool_manager.rb +1 -1
  18. data/lib/mongo/connection/socket.rb +18 -0
  19. data/lib/mongo/{util → connection/socket}/socket_util.rb +5 -2
  20. data/lib/mongo/{util → connection/socket}/ssl_socket.rb +3 -4
  21. data/lib/mongo/{util → connection/socket}/tcp_socket.rb +25 -15
  22. data/lib/mongo/{util → connection/socket}/unix_socket.rb +6 -4
  23. data/lib/mongo/cursor.rb +113 -47
  24. data/lib/mongo/db.rb +203 -131
  25. data/lib/mongo/{exceptions.rb → exception.rb} +7 -1
  26. data/lib/mongo/functional.rb +19 -0
  27. data/lib/mongo/functional/authentication.rb +303 -0
  28. data/lib/mongo/{util → functional}/logging.rb +1 -1
  29. data/lib/mongo/{util → functional}/read_preference.rb +49 -1
  30. data/lib/mongo/{util → functional}/uri_parser.rb +81 -69
  31. data/lib/mongo/{util → functional}/write_concern.rb +2 -1
  32. data/{test/unit/pool_test.rb → lib/mongo/gridfs.rb} +5 -10
  33. data/lib/mongo/gridfs/grid.rb +1 -3
  34. data/lib/mongo/gridfs/grid_ext.rb +1 -1
  35. data/lib/mongo/gridfs/grid_file_system.rb +1 -1
  36. data/lib/mongo/gridfs/grid_io.rb +1 -1
  37. data/lib/mongo/legacy.rb +63 -8
  38. data/lib/mongo/mongo_client.rb +128 -154
  39. data/lib/mongo/mongo_replica_set_client.rb +17 -11
  40. data/lib/mongo/mongo_sharded_client.rb +2 -1
  41. data/lib/mongo/networking.rb +19 -10
  42. data/lib/mongo/utils.rb +19 -0
  43. data/lib/mongo/{util → utils}/conversions.rb +1 -1
  44. data/lib/mongo/{util → utils}/core_ext.rb +1 -1
  45. data/lib/mongo/{util → utils}/server_version.rb +1 -1
  46. data/lib/mongo/{util → utils}/support.rb +10 -57
  47. data/lib/mongo/{util → utils}/thread_local_variable_manager.rb +1 -1
  48. data/test/functional/authentication_test.rb +8 -21
  49. data/test/functional/bulk_write_collection_view_test.rb +782 -0
  50. data/test/functional/{connection_test.rb → client_test.rb} +153 -78
  51. data/test/functional/collection_test.rb +343 -97
  52. data/test/functional/collection_writer_test.rb +83 -0
  53. data/test/functional/conversions_test.rb +1 -3
  54. data/test/functional/cursor_fail_test.rb +3 -3
  55. data/test/functional/cursor_message_test.rb +3 -3
  56. data/test/functional/cursor_test.rb +38 -3
  57. data/test/functional/db_api_test.rb +5 -5
  58. data/test/functional/db_connection_test.rb +2 -2
  59. data/test/functional/db_test.rb +35 -11
  60. data/test/functional/grid_file_system_test.rb +2 -2
  61. data/test/functional/grid_io_test.rb +2 -2
  62. data/test/functional/grid_test.rb +2 -2
  63. data/test/functional/pool_test.rb +2 -3
  64. data/test/functional/safe_test.rb +5 -5
  65. data/test/functional/ssl_test.rb +22 -102
  66. data/test/functional/support_test.rb +1 -1
  67. data/test/functional/timeout_test.rb +6 -22
  68. data/test/functional/uri_test.rb +113 -12
  69. data/test/functional/write_concern_test.rb +6 -6
  70. data/test/helpers/general.rb +50 -0
  71. data/test/helpers/test_unit.rb +309 -0
  72. data/test/replica_set/authentication_test.rb +8 -23
  73. data/test/replica_set/basic_test.rb +41 -14
  74. data/test/replica_set/client_test.rb +179 -117
  75. data/test/replica_set/complex_connect_test.rb +6 -7
  76. data/test/replica_set/connection_test.rb +46 -38
  77. data/test/replica_set/count_test.rb +2 -2
  78. data/test/replica_set/cursor_test.rb +8 -8
  79. data/test/replica_set/insert_test.rb +64 -2
  80. data/test/replica_set/max_values_test.rb +59 -10
  81. data/test/replica_set/pinning_test.rb +2 -2
  82. data/test/replica_set/query_test.rb +2 -2
  83. data/test/replica_set/read_preference_test.rb +6 -6
  84. data/test/replica_set/refresh_test.rb +7 -7
  85. data/test/replica_set/replication_ack_test.rb +5 -5
  86. data/test/replica_set/ssl_test.rb +24 -106
  87. data/test/sharded_cluster/basic_test.rb +43 -15
  88. data/test/shared/authentication/basic_auth_shared.rb +215 -0
  89. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  90. data/test/shared/ssl_shared.rb +173 -0
  91. data/test/test_helper.rb +31 -199
  92. data/test/threading/basic_test.rb +29 -3
  93. data/test/tools/mongo_config.rb +45 -20
  94. data/test/tools/mongo_config_test.rb +1 -1
  95. data/test/unit/client_test.rb +136 -57
  96. data/test/unit/collection_test.rb +31 -55
  97. data/test/unit/connection_test.rb +135 -72
  98. data/test/unit/cursor_test.rb +2 -2
  99. data/test/unit/db_test.rb +19 -15
  100. data/test/unit/grid_test.rb +2 -2
  101. data/test/unit/mongo_sharded_client_test.rb +17 -15
  102. data/test/unit/node_test.rb +2 -2
  103. data/test/unit/pool_manager_test.rb +7 -5
  104. data/test/unit/read_pref_test.rb +82 -2
  105. data/test/unit/read_test.rb +14 -14
  106. data/test/unit/safe_test.rb +9 -9
  107. data/test/unit/sharding_pool_manager_test.rb +11 -5
  108. data/test/unit/write_concern_test.rb +9 -9
  109. metadata +71 -56
  110. metadata.gz.sig +0 -0
  111. data/test/functional/threading_test.rb +0 -109
  112. data/test/shared/authentication.rb +0 -121
  113. data/test/unit/util_test.rb +0 -69
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013 10gen Inc.
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -12,9 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require 'socket'
16
- require 'thread'
17
-
18
15
  module Mongo
19
16
 
20
17
  # A MongoDB database.
@@ -27,6 +24,7 @@ module Mongo
27
24
  SYSTEM_USER_COLLECTION = 'system.users'
28
25
  SYSTEM_JS_COLLECTION = 'system.js'
29
26
  SYSTEM_COMMAND_COLLECTION = '$cmd'
27
+ MAX_TIME_MS_CODE = 50
30
28
 
31
29
  PROFILE_LEVEL = {
32
30
  :off => 0,
@@ -63,7 +61,10 @@ module Mongo
63
61
  attr_reader :name, :write_concern
64
62
 
65
63
  # The Mongo::MongoClient instance connecting to the MongoDB server.
66
- attr_reader :connection
64
+ attr_reader :client
65
+
66
+ # for backward compatibility
67
+ alias_method :connection, :client
67
68
 
68
69
  # The length of time that Collection.ensure_index should cache index calls
69
70
  attr_accessor :cache_time
@@ -99,81 +100,66 @@ module Mongo
99
100
  # on initialization or at the time of an operation.
100
101
  #
101
102
  # @option opts [Integer] :cache_time (300) Set the time that all ensure_index calls should cache the command.
102
- #
103
- # @core databases constructor_details
103
+
104
104
  def initialize(name, client, opts={})
105
- @name = Mongo::Support.validate_db_name(name)
106
- @connection = client
105
+ # A database name of '$external' is permitted for some auth types
106
+ Support.validate_db_name(name) unless name == '$external'
107
+
108
+ @name = name
109
+ @client = client
107
110
  @strict = opts[:strict]
108
111
  @pk_factory = opts[:pk]
109
112
 
110
113
  @write_concern = get_write_concern(opts, client)
111
114
 
112
- @read = opts[:read] || @connection.read
113
- Mongo::ReadPreference::validate(@read)
114
- @tag_sets = opts.fetch(:tag_sets, @connection.tag_sets)
115
- @acceptable_latency = opts.fetch(:acceptable_latency, @connection.acceptable_latency)
116
- @cache_time = opts[:cache_time] || 300 #5 minutes.
117
- end
115
+ @read = opts[:read] || @client.read
116
+ ReadPreference::validate(@read)
118
117
 
119
- # Authenticate with the given username and password. Note that mongod
120
- # must be started with the --auth option for authentication to be enabled.
121
- #
122
- # @param [String] username
123
- # @param [String] password
124
- # @param [Boolean] save_auth
125
- # Save this authentication to the client object using MongoClient#add_auth. This
126
- # will ensure that the authentication will be applied to all sockets and upon
127
- # database reconnect.
128
- # @param source [String] Database with user credentials. This should be used to
129
- # authenticate against a database when the credentials exist elsewhere.
130
- #
131
- # @note save_auth must be true when using connection pooling or providing a source
132
- # for credentials.
133
- #
134
- # @return [Boolean]
135
- #
136
- # @raise [AuthenticationError]
137
- #
138
- # @core authenticate authenticate-instance_method
139
- def authenticate(username, password=nil, save_auth=true, source=nil)
140
- if (@connection.pool_size > 1 || source) && !save_auth
141
- raise MongoArgumentError, "If using connection pooling or delegated auth, " +
142
- ":save_auth must be set to true."
143
- end
118
+ @tag_sets = opts.fetch(:tag_sets, @client.tag_sets)
119
+ @acceptable_latency = opts.fetch(:acceptable_latency,
120
+ @client.acceptable_latency)
144
121
 
145
- begin
146
- socket = @connection.checkout_reader(:mode => :primary_preferred)
147
- issue_authentication(username, password, save_auth,
148
- :socket => socket, :source => source)
149
- ensure
150
- socket.checkin if socket
151
- end
122
+ @cache_time = opts[:cache_time] || 300 #5 minutes.
123
+ end
152
124
 
153
- @connection.authenticate_pools
125
+ # Authenticate with the given username and password.
126
+ #
127
+ # @param username [String] The username.
128
+ # @param password [String] The user's password. This is not required for
129
+ # some authentication mechanisms.
130
+ # @param save_auth [Boolean]
131
+ # Save this authentication to the client object using
132
+ # MongoClient#add_auth. This will ensure that the authentication will
133
+ # be applied to all sockets and upon database reconnect.
134
+ # @param source [String] Database with user credentials. This should be
135
+ # used to authenticate against a database when the credentials exist
136
+ # elsewhere.
137
+ # @param mechanism [String] The authentication mechanism to be used.
138
+ #
139
+ # @note The ability to disable the save_auth option has been deprecated.
140
+ # With save_auth=false specified, driver authentication behavior during
141
+ # failovers and reconnections becomes unreliable. This option still
142
+ # exists for API compatibility, but it no longer has any effect if
143
+ # disabled and now always uses the default behavior (safe_auth=true).
144
+ #
145
+ # @raise [AuthenticationError] Raised if authentication fails.
146
+ # @return [Boolean] The result of the authentication operation.
147
+ def authenticate(username, password=nil, save_auth=nil, source=nil, mechanism=nil)
148
+ warn "[DEPRECATED] Disabling the 'save_auth' option no longer has " +
149
+ "any effect. Please see the API documentation for more details " +
150
+ "on this change." unless save_auth.nil?
151
+
152
+ @client.add_auth(self.name, username, password, source, mechanism)
154
153
  true
155
154
  end
156
155
 
157
- def issue_authentication(username, password, save_auth=true, opts={})
158
- doc = command({:getnonce => 1}, :check_response => false, :socket => opts[:socket])
159
- raise MongoDBError, "Error retrieving nonce: #{doc}" unless ok?(doc)
160
- nonce = doc['nonce']
161
-
162
- # issue authentication against this database if source option not provided
163
- source = opts[:source]
164
- db = source ? @connection[source] : self
165
-
166
- auth = BSON::OrderedHash.new
167
- auth['authenticate'] = 1
168
- auth['user'] = username
169
- auth['nonce'] = nonce
170
- auth['key'] = Mongo::Support.auth_key(username, password, nonce)
171
- if ok?(doc = db.command(auth, :check_response => false, :socket => opts[:socket]))
172
- @connection.add_auth(name, username, password, source) if save_auth
173
- else
174
- message = "Failed to authenticate user '#{username}' on db '#{db.name}'"
175
- raise Mongo::AuthenticationError.new(message, doc['code'], doc)
176
- end
156
+ # Deauthorizes use for this database for this client connection. Also removes
157
+ # the saved authentication in the MongoClient class associated with this
158
+ # database.
159
+ #
160
+ # @return [Boolean]
161
+ def logout(opts={})
162
+ @client.remove_auth(self.name)
177
163
  true
178
164
  end
179
165
 
@@ -224,18 +210,22 @@ module Mongo
224
210
  #
225
211
  # @return [Hash] an object representing the user.
226
212
  def add_user(username, password=nil, read_only=false, opts={})
227
- users = self[SYSTEM_USER_COLLECTION]
228
- user = users.find_one({:user => username}) || {:user => username}
229
- user['pwd'] = Mongo::Support.hash_password(username, password) if password
230
- user['readOnly'] = true if read_only
231
- user.merge!(opts)
232
213
  begin
233
- users.save(user)
214
+ user_info = command(:usersInfo => username)
215
+ # MongoDB >= 2.5.3 requires the use of commands to manage users.
216
+ # "Command not found" error didn't return an error code (59) before
217
+ # MongoDB 2.4.7 so we assume that a nil error code means the usersInfo
218
+ # command doesn't exist and we should fall back to the legacy add user code.
234
219
  rescue OperationFailure => ex
235
- # adding first admin user fails GLE in MongoDB 2.2
236
- raise ex unless ex.message =~ /login/
220
+ raise ex unless ex.error_code == Mongo::ErrorCode::COMMAND_NOT_FOUND || ex.error_code.nil?
221
+ return legacy_add_user(username, password, read_only, opts)
222
+ end
223
+
224
+ if user_info.key?('users') && !user_info['users'].empty?
225
+ update_user(username, password, opts)
226
+ else
227
+ create_user(username, password, read_only, opts)
237
228
  end
238
- user
239
229
  end
240
230
 
241
231
  # Remove the given user from this database. Returns false if the user
@@ -245,32 +235,13 @@ module Mongo
245
235
  #
246
236
  # @return [Boolean]
247
237
  def remove_user(username)
248
- if self[SYSTEM_USER_COLLECTION].find_one({:user => username})
249
- self[SYSTEM_USER_COLLECTION].remove({:user => username}, :w => 1)
250
- else
251
- false
252
- end
253
- end
254
-
255
- # Deauthorizes use for this database for this client connection. Also removes
256
- # any saved authentication in the MongoClient class associated with this
257
- # database.
258
- #
259
- # @raise [MongoDBError] if logging out fails.
260
- #
261
- # @return [Boolean]
262
- def logout(opts={})
263
- auth = @connection.auths.find { |a| a[:db_name] == name }
264
- db = auth && auth[:source] ? @connection[auth[:source]] : self
265
- auth ? @connection.logout_pools(db.name) : db.issue_logout(opts)
266
- @connection.remove_auth(db.name)
267
- end
268
-
269
- def issue_logout(opts={})
270
- unless ok?(doc = command({:logout => 1}, :socket => opts[:socket]))
271
- raise MongoDBError, "Error logging out: #{doc.inspect}"
238
+ begin
239
+ command(:dropUser => username)
240
+ rescue OperationFailure => ex
241
+ raise ex unless ex.error_code == Mongo::ErrorCode::COMMAND_NOT_FOUND || ex.error_code.nil?
242
+ response = self[SYSTEM_USER_COLLECTION].remove({:user => username}, :w => 1)
243
+ response.key?('n') && response['n'] > 0 ? response : false
272
244
  end
273
- true
274
245
  end
275
246
 
276
247
  # Get an array of collection names in this database.
@@ -451,6 +422,7 @@ module Mongo
451
422
 
452
423
  cmd = BSON::OrderedHash.new
453
424
  cmd[:$eval] = code
425
+ cmd.merge!(args.pop) if args.last.respond_to?(:keys) && args.last.key?(:nolock)
454
426
  cmd[:args] = args
455
427
  doc = command(cmd)
456
428
  doc['retval']
@@ -468,7 +440,7 @@ module Mongo
468
440
  cmd = BSON::OrderedHash.new
469
441
  cmd[:renameCollection] = "#{@name}.#{from}"
470
442
  cmd[:to] = "#{@name}.#{to}"
471
- doc = DB.new('admin', @connection).command(cmd, :check_response => false)
443
+ doc = DB.new('admin', @client).command(cmd, :check_response => false)
472
444
  ok?(doc) || raise(MongoDBError, "Error renaming collection: #{doc.inspect}")
473
445
  end
474
446
 
@@ -509,7 +481,7 @@ module Mongo
509
481
  #
510
482
  # @return [Hash]
511
483
  def stats
512
- self.command({:dbstats => 1})
484
+ self.command(:dbstats => 1)
513
485
  end
514
486
 
515
487
  # Return +true+ if the supplied +doc+ contains an 'ok' field with the value 1.
@@ -531,8 +503,8 @@ module Mongo
531
503
  # to see how it works.
532
504
  #
533
505
  # @param [OrderedHash, Hash] selector an OrderedHash, or a standard Hash with just one
534
- # key, specifying the command to be performed. In Ruby 1.9, OrderedHash isn't necessary since
535
- # hashes are ordered by default.
506
+ # key, specifying the command to be performed. In Ruby 1.9 and above, OrderedHash isn't necessary
507
+ # because hashes are ordered by default.
536
508
  #
537
509
  # @option opts [Boolean] :check_response (true) If +true+, raises an exception if the
538
510
  # command fails.
@@ -540,48 +512,59 @@ module Mongo
540
512
  # @option opts [:primary, :secondary] :read Read preference for this command. See Collection#find for
541
513
  # more details.
542
514
  # @option opts [String] :comment (nil) a comment to include in profiling logs
515
+ # @option opts [Boolean] :compile_regex (true) whether BSON regex objects should be compiled into Ruby regexes.
516
+ # If false, a BSON::Regex object will be returned instead.
543
517
  #
544
518
  # @return [Hash]
545
- #
546
- # @core commands command_instance-method
547
519
  def command(selector, opts={})
548
- check_response = opts.fetch(:check_response, true)
549
- socket = opts[:socket]
550
- raise MongoArgumentError, "Command must be given a selector" unless selector.is_a?(Hash) && !selector.empty?
551
-
552
- if selector.keys.length > 1 && RUBY_VERSION < '1.9' && selector.class != BSON::OrderedHash
553
- raise MongoArgumentError, "DB#command requires an OrderedHash when hash contains multiple keys"
554
- end
555
-
556
- if read_pref = opts[:read]
557
- Mongo::ReadPreference::validate(read_pref)
558
- unless read_pref == :primary || Mongo::Support::secondary_ok?(selector)
559
- raise MongoArgumentError, "Command is not supported on secondaries: #{selector.keys.first}"
520
+ raise MongoArgumentError, "Command must be given a selector" unless selector.respond_to?(:keys) && !selector.empty?
521
+
522
+ opts = opts.dup
523
+ # deletes :check_response and returns the value, if nil defaults to the block result
524
+ check_response = opts.delete(:check_response) { true }
525
+
526
+ # build up the command hash
527
+ command = opts.key?(:socket) ? { :socket => opts.delete(:socket) } : {}
528
+ command.merge!(:comment => opts.delete(:comment)) if opts.key?(:comment)
529
+ command.merge!(:compile_regex => opts.delete(:compile_regex)) if opts.key?(:compile_regex)
530
+ command[:limit] = -1
531
+ command[:read] = Mongo::ReadPreference::cmd_read_pref(opts.delete(:read), selector) if opts.key?(:read)
532
+
533
+ if RUBY_VERSION < '1.9' && selector.class != BSON::OrderedHash
534
+ if selector.keys.length > 1
535
+ raise MongoArgumentError, "DB#command requires an OrderedHash when hash contains multiple keys"
536
+ end
537
+ if opts.keys.size > 0
538
+ # extra opts will be merged into the selector, so make sure it's an OH in versions < 1.9
539
+ selector = selector.dup
540
+ selector = BSON::OrderedHash.new.merge!(selector)
560
541
  end
561
542
  end
562
543
 
544
+ # arbitrary opts are merged into the selector
545
+ command[:selector] = selector.merge!(opts)
546
+
563
547
  begin
564
- result = Cursor.new(
565
- system_command_collection,
566
- :limit => -1,
567
- :selector => selector,
568
- :socket => socket,
569
- :read => read_pref,
570
- :comment => opts[:comment]).next_document
548
+ result = Cursor.new(system_command_collection, command).next_document
571
549
  rescue OperationFailure => ex
572
- raise OperationFailure, "Database command '#{selector.keys.first}' failed: #{ex.message}"
550
+ if check_response
551
+ raise OperationFailure.new("Database command '#{selector.keys.first}' failed: #{ex.message}", ex.error_code, ex.result)
552
+ else
553
+ result = ex.result
554
+ end
573
555
  end
574
556
 
575
557
  raise OperationFailure,
576
558
  "Database command '#{selector.keys.first}' failed: returned null." unless result
577
559
 
578
- if check_response && !ok?(result)
560
+ if check_response && (!ok?(result) || result['writeErrors'] || result['writeConcernError'])
579
561
  message = "Database command '#{selector.keys.first}' failed: ("
580
562
  message << result.map do |key, value|
581
563
  "#{key}: '#{value}'"
582
564
  end.join('; ')
583
565
  message << ').'
584
566
  code = result['code'] || result['assertionCode']
567
+ raise ExecutionTimeout.new(message, code, result) if code == MAX_TIME_MS_CODE
585
568
  raise OperationFailure.new(message, code, result)
586
569
  end
587
570
 
@@ -618,8 +601,6 @@ module Mongo
618
601
  # get the results using DB#profiling_info.
619
602
  #
620
603
  # @return [Symbol] :off, :slow_only, or :all
621
- #
622
- # @core profiling profiling_level-instance_method
623
604
  def profiling_level
624
605
  cmd = BSON::OrderedHash.new
625
606
  cmd[:profile] = -1
@@ -677,5 +658,96 @@ module Mongo
677
658
  def system_command_collection
678
659
  Collection.new(SYSTEM_COMMAND_COLLECTION, self)
679
660
  end
661
+
662
+ # Create a new user.
663
+ #
664
+ # @param username [String] The username.
665
+ # @param password [String] The user's password.
666
+ # @param read_only [Boolean] Create a read-only user (deprecated in MongoDB >= 2.6)
667
+ # @param opts [Hash]
668
+ #
669
+ # @private
670
+ def create_user(username, password, read_only, opts)
671
+ if read_only || !opts.key?(:roles)
672
+ warn "Creating a user with the read_only option or without roles is " +
673
+ "deprecated in MongoDB >= 2.6"
674
+ end
675
+
676
+ # The password is always salted and hashed by the driver.
677
+ if opts.key?(:digestPassword)
678
+ raise MongoArgumentError,
679
+ "The digestPassword option is not available via DB#add_user. " +
680
+ "Use DB#command(:createUser => ...) instead for this option."
681
+ end
682
+
683
+ opts = opts.dup
684
+ pwd = Mongo::Authentication.hash_password(username, password) if password
685
+ create_opts = pwd ? { :pwd => pwd } : {}
686
+ # specify that the server shouldn't digest the password because the driver does
687
+ create_opts[:digestPassword] = false
688
+ unless opts.key?(:roles)
689
+ if name == 'admin'
690
+ roles = read_only ? ['readAnyDatabase'] : ['root']
691
+ else
692
+ roles = read_only ? ['read'] : ["dbOwner"]
693
+ end
694
+ create_opts[:roles] = roles
695
+ end
696
+ create_opts[:writeConcern] =
697
+ opts.key?(:writeConcern) ? opts.delete(:writeConcern) : { :w => 1 }
698
+ create_opts.merge!(opts)
699
+ command({ :createUser => username }, create_opts)
700
+ end
701
+
702
+ # Update a user.
703
+ #
704
+ # @param username [String] The username.
705
+ # @param password [String] The user's password.
706
+ # @param opts [Hash]
707
+ #
708
+ # @private
709
+ def update_user(username, password, opts)
710
+ # The password is always salted and hashed by the driver.
711
+ if opts.key?(:digestPassword)
712
+ raise MongoArgumentError,
713
+ "The digestPassword option is not available via DB#add_user. " +
714
+ "Use DB#command(:createUser => ...) instead for this option."
715
+ end
716
+
717
+ opts = opts.dup
718
+ pwd = Mongo::Authentication.hash_password(username, password) if password
719
+ update_opts = pwd ? { :pwd => pwd } : {}
720
+ # specify that the server shouldn't digest the password because the driver does
721
+ update_opts[:digestPassword] = false
722
+ update_opts[:writeConcern] =
723
+ opts.key?(:writeConcern) ? opts.delete(:writeConcern) : { :w => 1 }
724
+ update_opts.merge!(opts)
725
+ command({ :updateUser => username }, update_opts)
726
+ end
727
+
728
+ # Create a user in MongoDB versions < 2.5.3.
729
+ # Called by #add_user if the 'usersInfo' command fails.
730
+ #
731
+ # @param username [String] The username.
732
+ # @param password [String] (nil) The user's password.
733
+ # @param read_only [Boolean] (false) Create a read-only user.
734
+ # @param opts [Hash]
735
+ #
736
+ # @private
737
+ def legacy_add_user(username, password=nil, read_only=false, opts={})
738
+ users = self[SYSTEM_USER_COLLECTION]
739
+ user = users.find_one(:user => username) || {:user => username}
740
+ user['pwd'] =
741
+ Mongo::Authentication.hash_password(username, password) if password
742
+ user['readOnly'] = true if read_only
743
+ user.merge!(opts)
744
+ begin
745
+ users.save(user)
746
+ rescue OperationFailure => ex
747
+ # adding first admin user fails GLE in MongoDB 2.2
748
+ raise ex unless ex.message =~ /login/
749
+ end
750
+ user
751
+ end
680
752
  end
681
753
  end