mongo 2.7.2 → 2.8.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -3
  4. data/lib/mongo/address.rb +17 -20
  5. data/lib/mongo/address/ipv4.rb +6 -3
  6. data/lib/mongo/address/ipv6.rb +6 -3
  7. data/lib/mongo/address/unix.rb +5 -2
  8. data/lib/mongo/auth.rb +15 -2
  9. data/lib/mongo/auth/cr/conversation.rb +4 -2
  10. data/lib/mongo/auth/ldap/conversation.rb +4 -2
  11. data/lib/mongo/auth/scram.rb +3 -7
  12. data/lib/mongo/auth/scram/conversation.rb +28 -19
  13. data/lib/mongo/auth/user.rb +45 -10
  14. data/lib/mongo/auth/x509/conversation.rb +4 -2
  15. data/lib/mongo/cluster.rb +9 -17
  16. data/lib/mongo/error.rb +2 -0
  17. data/lib/mongo/error/missing_password.rb +29 -0
  18. data/lib/mongo/error/operation_failure.rb +7 -3
  19. data/lib/mongo/error/parser.rb +2 -1
  20. data/lib/mongo/error/sdam_error_detection.rb +54 -0
  21. data/lib/mongo/operation/aggregate/command.rb +1 -16
  22. data/lib/mongo/operation/aggregate/op_msg.rb +1 -1
  23. data/lib/mongo/operation/collections_info.rb +2 -3
  24. data/lib/mongo/operation/delete/command.rb +2 -15
  25. data/lib/mongo/operation/delete/legacy.rb +1 -16
  26. data/lib/mongo/operation/explain/command.rb +1 -16
  27. data/lib/mongo/operation/explain/legacy.rb +1 -16
  28. data/lib/mongo/operation/find/command.rb +1 -16
  29. data/lib/mongo/operation/find/legacy.rb +1 -16
  30. data/lib/mongo/operation/get_more/command.rb +1 -16
  31. data/lib/mongo/operation/indexes/command.rb +1 -16
  32. data/lib/mongo/operation/indexes/legacy.rb +4 -16
  33. data/lib/mongo/operation/list_collections/command.rb +1 -16
  34. data/lib/mongo/operation/map_reduce/command.rb +1 -16
  35. data/lib/mongo/operation/parallel_scan/command.rb +1 -16
  36. data/lib/mongo/operation/result.rb +3 -0
  37. data/lib/mongo/operation/shared/executable.rb +4 -0
  38. data/lib/mongo/operation/shared/polymorphic_lookup.rb +1 -1
  39. data/lib/mongo/operation/shared/polymorphic_result.rb +8 -1
  40. data/lib/mongo/operation/shared/result/aggregatable.rb +0 -5
  41. data/lib/mongo/operation/update/command.rb +2 -15
  42. data/lib/mongo/operation/update/legacy.rb +1 -16
  43. data/lib/mongo/operation/users_info/command.rb +1 -16
  44. data/lib/mongo/retryable.rb +22 -10
  45. data/lib/mongo/server.rb +10 -1
  46. data/lib/mongo/server/app_metadata.rb +7 -2
  47. data/lib/mongo/server/connectable.rb +0 -6
  48. data/lib/mongo/server/connection.rb +86 -135
  49. data/lib/mongo/server/connection_base.rb +133 -0
  50. data/lib/mongo/server/connection_pool.rb +11 -24
  51. data/lib/mongo/server/connection_pool/queue.rb +41 -41
  52. data/lib/mongo/server/description.rb +1 -1
  53. data/lib/mongo/server/monitor.rb +4 -4
  54. data/lib/mongo/server/monitor/connection.rb +26 -7
  55. data/lib/mongo/server/pending_connection.rb +36 -0
  56. data/lib/mongo/server_selector/selectable.rb +9 -1
  57. data/lib/mongo/session.rb +0 -1
  58. data/lib/mongo/socket.rb +23 -6
  59. data/lib/mongo/socket/ssl.rb +11 -18
  60. data/lib/mongo/socket/tcp.rb +13 -14
  61. data/lib/mongo/socket/unix.rb +9 -27
  62. data/lib/mongo/uri.rb +1 -1
  63. data/lib/mongo/version.rb +1 -1
  64. data/spec/integration/auth_spec.rb +160 -0
  65. data/spec/integration/retryable_writes_spec.rb +55 -58
  66. data/spec/integration/sdam_error_handling_spec.rb +115 -0
  67. data/spec/mongo/address/ipv4_spec.rb +4 -0
  68. data/spec/mongo/address/ipv6_spec.rb +4 -0
  69. data/spec/mongo/auth/scram/conversation_spec.rb +6 -5
  70. data/spec/mongo/auth/scram/negotiation_spec.rb +25 -36
  71. data/spec/mongo/auth/scram_spec.rb +2 -2
  72. data/spec/mongo/auth/user_spec.rb +97 -0
  73. data/spec/mongo/client_construction_spec.rb +1 -1
  74. data/spec/mongo/error/operation_failure_spec.rb +125 -1
  75. data/spec/mongo/retryable_spec.rb +17 -8
  76. data/spec/mongo/server/connection_pool/queue_spec.rb +24 -10
  77. data/spec/mongo/server/connection_pool_spec.rb +30 -117
  78. data/spec/mongo/server/connection_spec.rb +147 -25
  79. data/spec/mongo/server/description_spec.rb +0 -14
  80. data/spec/mongo/server/monitor/connection_spec.rb +22 -0
  81. data/spec/mongo/server_selector_spec.rb +1 -0
  82. data/spec/mongo/server_spec.rb +6 -6
  83. data/spec/mongo/socket/ssl_spec.rb +48 -116
  84. data/spec/mongo/socket/tcp_spec.rb +22 -0
  85. data/spec/mongo/socket/unix_spec.rb +9 -9
  86. data/spec/mongo/socket_spec.rb +15 -3
  87. data/spec/spec_tests/server_selection_spec.rb +2 -0
  88. data/spec/support/client_registry.rb +8 -2
  89. data/spec/support/common_shortcuts.rb +20 -1
  90. data/spec/support/constraints.rb +10 -2
  91. data/spec/support/lite_constraints.rb +8 -0
  92. data/spec/support/spec_config.rb +9 -1
  93. metadata +14 -4
  94. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62add400ebd524931d5abfbd049bb386749820fb158a2fbbbc68290dc06422d6
4
- data.tar.gz: 76fe35f188981942710738798c9f97422a5058e2b5bac3bbd4c73fdc38ce40d9
3
+ metadata.gz: 5c0c1dd6dd1ce8ec0ce98e4f038bb188a743d65b0fd45f42383e732b8a9fe6e0
4
+ data.tar.gz: 69a9fcd5c2284e95b7af207ae381bc5fb4b9a0d00d941b54bb7d86a126a62e70
5
5
  SHA512:
6
- metadata.gz: 6d1530a695332219b809454e62e6b5422afea009a8ea2e4b32678e5763f663c07d839078e74b88d42d40fe32dea3b82760bf94f055ee72071e78f57eabe92771
7
- data.tar.gz: 409a749f3d3bd253bf74bf3bb6fbe1573e158314f2579f34c2922f5154aa44b5a1ab509ab50f4da5978940682a9e4abe59137d4541d8225fc2bba5eef24491c6
6
+ metadata.gz: eb0c350dc452f0eb60656e0a2e6657e0002a7760de5bb3af7820af77876046f48bd7946e8d7d39a727dd80eda43e48656db52b941c2ab45ea6096ba826049a4d
7
+ data.tar.gz: d13ac4eec40c58e932f61fe5cb3f52259ca77a8ed08f079edf910927b9bb5e0c0ec77e0461374ab6c0f783726b1a5c7972e12c63c8a6d2eb46a6170a80543538
Binary file
data.tar.gz.sig CHANGED
@@ -1,3 +1 @@
1
- �\n�"���;��ۣ�H�I
2
- (�GIB�U"oS��8����x|�F}W G�H�-*Ĝr`��f�%�R�M{Go_c'g��˛�'u��?����5LxI�@�x�R<���TԤ���*h�>��.�Fd�*��{������$u~����E�mmE�@pE
3
- kRd���)C�&�q��CM*��K���?mܴن�1#K$f렸E��u�}6N�
1
+ ��zW�Y���MH{�zG.�vr�fτ#V���?�Y���b�;�86�o\z�E^����V�b��K=��:����G���壘s��yҌ�uA[݁XSG�Z9>�|�<DZ�Ԍ~2�&�v2�Ȭf/p�n���� ���-�WLfUrl,�s_��h'nn����q�;7��vD#�0y}�j"r����H�RjN�8=4����YQ�@d#� ԉޮf��.�:��r�b�ӯ�Eʎ��ӗ,��j�_��
@@ -147,12 +147,15 @@ module Mongo
147
147
  #
148
148
  # @param [ Float ] socket_timeout The socket timeout.
149
149
  # @param [ Hash ] ssl_options SSL options.
150
+ # @param [ Hash ] options The options.
151
+ #
152
+ # @option options [ Float ] :connect_timeout Connect timeout.
150
153
  #
151
154
  # @return [ Mongo::Socket::SSL, Mongo::Socket::TCP, Mongo::Socket::Unix ] The socket.
152
155
  #
153
156
  # @since 2.0.0
154
- def socket(socket_timeout, ssl_options = {})
155
- create_resolver(ssl_options).socket(socket_timeout, ssl_options)
157
+ def socket(socket_timeout, ssl_options = {}, options = {})
158
+ create_resolver(ssl_options).socket(socket_timeout, ssl_options, options)
156
159
  end
157
160
 
158
161
  # Get the address as a string.
@@ -175,25 +178,17 @@ module Mongo
175
178
  end
176
179
  end
177
180
 
178
- # Connect a socket.
179
- #
180
- # @example Connect a socket.
181
- # address.connect_socket!(socket)
182
- #
183
- # @since 2.4.3
184
- def connect_socket!(socket)
185
- socket.connect!(connect_timeout)
186
- end
187
-
188
- private
189
-
181
+ # @api private
190
182
  def connect_timeout
191
183
  @connect_timeout ||= @options[:connect_timeout] || Server::CONNECT_TIMEOUT
192
184
  end
193
185
 
194
- # To determine which address the socket will connect to, the driver will attempt to connect to
195
- # each IP address returned by Socket::getaddrinfo in sequence. Once a successful connection is
196
- # made, a resolver with that IP address specified is returned. If no successful connection is
186
+ private
187
+
188
+ # To determine which address the socket will connect to, the driver will
189
+ # attempt to connect to each IP address returned by Socket::getaddrinfo in
190
+ # sequence. Once a successful connection is made, a resolver with that
191
+ # IP address specified is returned. If no successful connection is
197
192
  # made, the error made by the last connection attempt is raised.
198
193
  def create_resolver(ssl_options)
199
194
  return Unix.new(seed.downcase) if seed.downcase =~ Unix::MATCH
@@ -202,9 +197,11 @@ module Mongo
202
197
  error = nil
203
198
  ::Socket.getaddrinfo(host, nil, family, ::Socket::SOCK_STREAM).each do |info|
204
199
  begin
205
- res = FAMILY_MAP[info[4]].new(info[3], port, host)
206
- res.socket(connect_timeout, ssl_options).connect!(connect_timeout).close
207
- return res
200
+ specific_address = FAMILY_MAP[info[4]].new(info[3], port, host)
201
+ socket = specific_address.socket(
202
+ connect_timeout, ssl_options, connect_timeout: connect_timeout)
203
+ socket.close
204
+ return specific_address
208
205
  rescue IOError, SystemCallError, Error::SocketTimeoutError, Error::SocketError => e
209
206
  error = e
210
207
  end
@@ -79,15 +79,18 @@ module Mongo
79
79
  #
80
80
  # @param [ Float ] socket_timeout The socket timeout.
81
81
  # @param [ Hash ] ssl_options SSL options.
82
+ # @param [ Hash ] options The options.
83
+ #
84
+ # @option options [ Float ] :connect_timeout Connect timeout.
82
85
  #
83
86
  # @return [ Mongo::Socket::SSL, Mongo::Socket::TCP ] The socket.
84
87
  #
85
88
  # @since 2.0.0
86
- def socket(socket_timeout, ssl_options = {})
89
+ def socket(socket_timeout, ssl_options = {}, options = {})
87
90
  unless ssl_options.empty?
88
- Socket::SSL.new(host, port, host_name, socket_timeout, Socket::PF_INET, ssl_options)
91
+ Socket::SSL.new(host, port, host_name, socket_timeout, Socket::PF_INET, ssl_options.merge(options))
89
92
  else
90
- Socket::TCP.new(host, port, socket_timeout, Socket::PF_INET)
93
+ Socket::TCP.new(host, port, socket_timeout, Socket::PF_INET, options)
91
94
  end
92
95
  end
93
96
  end
@@ -95,15 +95,18 @@ module Mongo
95
95
  #
96
96
  # @param [ Float ] socket_timeout The socket timeout.
97
97
  # @param [ Hash ] ssl_options SSL options.
98
+ # @param [ Hash ] options The options.
99
+ #
100
+ # @option options [ Float ] :connect_timeout Connect timeout.
98
101
  #
99
102
  # @return [ Mongo::Socket::SSL, Mongo::Socket::TCP ] The socket.
100
103
  #
101
104
  # @since 2.0.0
102
- def socket(socket_timeout, ssl_options = {})
105
+ def socket(socket_timeout, ssl_options = {}, options = {})
103
106
  unless ssl_options.empty?
104
- Socket::SSL.new(host, port, host_name, socket_timeout, Socket::PF_INET6, ssl_options)
107
+ Socket::SSL.new(host, port, host_name, socket_timeout, Socket::PF_INET6, ssl_options.merge(options))
105
108
  else
106
- Socket::TCP.new(host, port, socket_timeout, Socket::PF_INET6)
109
+ Socket::TCP.new(host, port, socket_timeout, Socket::PF_INET6, options)
107
110
  end
108
111
  end
109
112
  end
@@ -64,12 +64,15 @@ module Mongo
64
64
  #
65
65
  # @param [ Float ] socket_timeout The socket timeout.
66
66
  # @param [ Hash ] ssl_options SSL options - ignored.
67
+ # @param [ Hash ] options The options.
68
+ #
69
+ # @option options [ Float ] :connect_timeout Connect timeout.
67
70
  #
68
71
  # @return [ Mongo::Socket::Unix ] The socket.
69
72
  #
70
73
  # @since 2.0.0
71
- def socket(socket_timeout, ssl_options = {})
72
- Socket::Unix.new(host, socket_timeout)
74
+ def socket(socket_timeout, ssl_options = {}, options = {})
75
+ Socket::Unix.new(host, socket_timeout, options)
73
76
  end
74
77
  end
75
78
  end
@@ -102,10 +102,23 @@ module Mongo
102
102
  # Mongo::Auth::Unauthorized.new(user)
103
103
  #
104
104
  # @param [ Mongo::Auth::User ] user The unauthorized user.
105
+ # @param [ String ] used_mechanism Auth mechanism actually used for
106
+ # authentication. This is a full string like SCRAM-SHA-256.
105
107
  #
106
108
  # @since 2.0.0
107
- def initialize(user)
108
- super("User #{user.name} is not authorized to access #{user.database}.")
109
+ def initialize(user, used_mechanism = nil)
110
+ specified_mechanism = if user.mechanism
111
+ " (mechanism: #{user.mechanism})"
112
+ else
113
+ ''
114
+ end
115
+ used_mechanism = if used_mechanism
116
+ " (used mechanism: #{used_mechanism})"
117
+ else
118
+ ''
119
+ end
120
+ msg = "User #{user.name}#{specified_mechanism} is not authorized to access #{user.database}#{used_mechanism}"
121
+ super(msg)
109
122
  end
110
123
  end
111
124
  end
@@ -92,7 +92,7 @@ module Mongo
92
92
  end
93
93
 
94
94
  # Start the CR conversation. This returns the first message that
95
- # needs to be send to the server.
95
+ # needs to be sent to the server.
96
96
  #
97
97
  # @example Start the conversation.
98
98
  # conversation.start
@@ -130,7 +130,9 @@ module Mongo
130
130
  private
131
131
 
132
132
  def validate!(reply)
133
- raise Unauthorized.new(user) if reply.documents[0][Operation::Result::OK] != 1
133
+ if reply.documents[0][Operation::Result::OK] != 1
134
+ raise Unauthorized.new(user, MECHANISM)
135
+ end
134
136
  @nonce = reply.documents[0][Auth::NONCE]
135
137
  @reply = reply
136
138
  end
@@ -51,7 +51,7 @@ module Mongo
51
51
  end
52
52
 
53
53
  # Start the PLAIN conversation. This returns the first message that
54
- # needs to be send to the server.
54
+ # needs to be sent to the server.
55
55
  #
56
56
  # @example Start the conversation.
57
57
  # conversation.start
@@ -97,7 +97,9 @@ module Mongo
97
97
  end
98
98
 
99
99
  def validate!(reply)
100
- raise Unauthorized.new(user) if reply.documents[0][Operation::Result::OK] != 1
100
+ if reply.documents[0][Operation::Result::OK] != 1
101
+ raise Unauthorized.new(user, MECHANISM)
102
+ end
101
103
  @reply = reply
102
104
  end
103
105
  end
@@ -32,7 +32,6 @@ module Mongo
32
32
  # @since 2.6.0
33
33
  SCRAM_SHA_256_MECHANISM = 'SCRAM-SHA-256'.freeze
34
34
 
35
-
36
35
  # Map the user-specified authentication mechanism to the proper names of the mechanisms.
37
36
  #
38
37
  # @since 2.6.0
@@ -62,16 +61,13 @@ module Mongo
62
61
  # user.login(connection)
63
62
  #
64
63
  # @param [ Mongo::Connection ] connection The connection to log into.
65
- # on.
66
- # @param [ String ] mechanism The auth mechanism to use (either 'SCRAM-SHA-1' or
67
- # 'SCRAM-SHA-256');
68
64
  #
69
65
  # @return [ Protocol::Message ] The authentication response.
70
66
  #
71
67
  # @since 2.0.0
72
- def login(connection, mechanism = nil)
73
- mechanism ||= user.mechanism || :scram
74
- conversation = Conversation.new(user, MECHANISMS[mechanism])
68
+ def login(connection)
69
+ mechanism = user.mechanism || :scram
70
+ conversation = Conversation.new(user, mechanism)
75
71
  reply = connection.dispatch([ conversation.start(connection) ])
76
72
  connection.update_cluster_time(Operation::Result.new(reply))
77
73
  reply = connection.dispatch([ conversation.continue(reply, connection) ])
@@ -19,8 +19,8 @@ module Mongo
19
19
  module Auth
20
20
  class SCRAM
21
21
 
22
- # Defines behavior around a single SCRAM-SHA-1 conversation between the
23
- # client and server.
22
+ # Defines behavior around a single SCRAM-SHA-1/256 conversation between
23
+ # the client and server.
24
24
  #
25
25
  # @since 2.0.0
26
26
  class Conversation
@@ -168,7 +168,7 @@ module Mongo
168
168
  end
169
169
 
170
170
  # Start the SCRAM conversation. This returns the first message that
171
- # needs to be send to the server.
171
+ # needs to be sent to the server.
172
172
  #
173
173
  # @example Start the conversation.
174
174
  # conversation.start
@@ -180,7 +180,8 @@ module Mongo
180
180
  # @since 2.0.0
181
181
  def start(connection = nil)
182
182
  if connection && connection.features.op_msg_enabled?
183
- selector = CLIENT_FIRST_MESSAGE.merge(payload: client_first_message, mechanism: @mechanism)
183
+ selector = CLIENT_FIRST_MESSAGE.merge(
184
+ payload: client_first_message, mechanism: full_mechanism)
184
185
  selector[Protocol::Msg::DATABASE_IDENTIFIER] = user.auth_source
185
186
  cluster_time = connection.mongos? && connection.cluster_time
186
187
  selector[Operation::CLUSTER_TIME] = cluster_time if cluster_time
@@ -189,12 +190,17 @@ module Mongo
189
190
  Protocol::Query.new(
190
191
  user.auth_source,
191
192
  Database::COMMAND,
192
- CLIENT_FIRST_MESSAGE.merge(payload: client_first_message, mechanism: @mechanism),
193
+ CLIENT_FIRST_MESSAGE.merge(
194
+ payload: client_first_message, mechanism: full_mechanism),
193
195
  limit: -1
194
196
  )
195
197
  end
196
198
  end
197
199
 
200
+ def full_mechanism
201
+ MECHANISMS[@mechanism]
202
+ end
203
+
198
204
  # Get the id of the conversation.
199
205
  #
200
206
  # @example Get the id of the conversation.
@@ -213,9 +219,14 @@ module Mongo
213
219
  # Conversation.new(user, mechanism)
214
220
  #
215
221
  # @param [ Auth::User ] user The user to converse about.
222
+ # @param [ Symbol ] mechanism Authentication mechanism.
216
223
  #
217
224
  # @since 2.0.0
218
225
  def initialize(user, mechanism)
226
+ unless [:scram, :scram256].include?(mechanism)
227
+ raise InvalidMechanism.new(mechanism)
228
+ end
229
+
219
230
  @user = user
220
231
  @nonce = SecureRandom.base64
221
232
  @client_key = user.send(:client_key)
@@ -343,7 +354,7 @@ module Mongo
343
354
  # @since 2.0.0
344
355
  def hi(data)
345
356
  case @mechanism
346
- when SCRAM::SCRAM_SHA_256_MECHANISM
357
+ when :scram256
347
358
  OpenSSL::PKCS5.pbkdf2_hmac(
348
359
  data,
349
360
  Base64.strict_decode64(salt),
@@ -381,7 +392,7 @@ module Mongo
381
392
  @iterations ||= payload_data.match(ITERATIONS)[1].to_i.tap do |i|
382
393
  if i < MIN_ITER_COUNT
383
394
  raise Error::InsufficientIterationCount.new(
384
- Error::InsufficientIterationCount.message(MIN_ITER_COUNT, 1))
395
+ Error::InsufficientIterationCount.message(MIN_ITER_COUNT, i))
385
396
  end
386
397
  end
387
398
  end
@@ -421,7 +432,12 @@ module Mongo
421
432
  #
422
433
  # @since 2.0.0
423
434
  def salted_password
424
- @salted_password ||= hi(hashed_password)
435
+ @salted_password ||= case @mechanism
436
+ when :scram256
437
+ hi(user.sasl_prepped_password)
438
+ else
439
+ hi(user.hashed_password)
440
+ end
425
441
  end
426
442
 
427
443
  # Server key algorithm implementation.
@@ -505,24 +521,17 @@ module Mongo
505
521
  end
506
522
 
507
523
  def validate!(reply)
508
- raise Unauthorized.new(user) unless reply.documents[0][Operation::Result::OK] == 1
524
+ if reply.documents[0][Operation::Result::OK] != 1
525
+ raise Unauthorized.new(user, full_mechanism)
526
+ end
509
527
  @reply = reply
510
528
  end
511
529
 
512
530
  private
513
531
 
514
- def hashed_password
515
- case @mechanism
516
- when SCRAM::SCRAM_SHA_256_MECHANISM
517
- user.sasl_prepped_hashed_password
518
- else
519
- user.hashed_password
520
- end
521
- end
522
-
523
532
  def digest
524
533
  @digest ||= case @mechanism
525
- when SCRAM::SCRAM_SHA_256_MECHANISM
534
+ when :scram256
526
535
  OpenSSL::Digest::SHA256.new.freeze
527
536
  else
528
537
  OpenSSL::Digest::SHA1.new.freeze
@@ -21,6 +21,7 @@ module Mongo
21
21
  #
22
22
  # @since 2.0.0
23
23
  class User
24
+ include Loggable
24
25
 
25
26
  # @return [ String ] The authorization source, either a database or
26
27
  # external name.
@@ -44,6 +45,14 @@ module Mongo
44
45
  # @return [ Array<String> ] roles The user roles.
45
46
  attr_reader :roles
46
47
 
48
+ # Loggable requires an options attribute. We don't have any options
49
+ # hence provide this as a stub.
50
+ #
51
+ # @api private
52
+ def options
53
+ {}
54
+ end
55
+
47
56
  # Determine if this user is equal to another.
48
57
  #
49
58
  # @example Check user equality.
@@ -99,7 +108,7 @@ module Mongo
99
108
  [ name, database, password ].hash
100
109
  end
101
110
 
102
- # Get the user's hashed password.
111
+ # Get the user's hashed password for SCRAM-SHA-1.
103
112
  #
104
113
  # @example Get the user's hashed password.
105
114
  # user.hashed_password
@@ -108,11 +117,25 @@ module Mongo
108
117
  #
109
118
  # @since 2.0.0
110
119
  def hashed_password
120
+ unless password
121
+ raise Error::MissingPassword
122
+ end
123
+
111
124
  @hashed_password ||= Digest::MD5.hexdigest("#{name}:mongo:#{password}").encode(BSON::UTF8)
112
125
  end
113
126
 
114
- def sasl_prepped_hashed_password
115
- @sasl_prepped_hashed_password ||= sasl_prepped_password.encode(BSON::UTF8)
127
+ # Get the user's stringprepped password for SCRAM-SHA-256.
128
+ #
129
+ # @api private
130
+ def sasl_prepped_password
131
+ unless password
132
+ raise Error::MissingPassword
133
+ end
134
+
135
+ @sasl_prepped_password ||= StringPrep.prepare(password,
136
+ StringPrep::Profiles::SASL::MAPPINGS,
137
+ StringPrep::Profiles::SASL::PROHIBITED,
138
+ normalize: true, bidi: true).encode(BSON::UTF8)
116
139
  end
117
140
 
118
141
  # Create the new user.
@@ -140,6 +163,25 @@ module Mongo
140
163
  @name = options[:user]
141
164
  @password = options[:password] || options[:pwd]
142
165
  @mechanism = options[:auth_mech]
166
+ if @mechanism
167
+ # Since the driver must select an authentication class for
168
+ # the specified mechanism, mechanisms that the driver does not
169
+ # know about, and cannot translate to an authentication class,
170
+ # need to be rejected.
171
+ unless @mechanism.is_a?(Symbol)
172
+ # Although we documented auth_mech option as being a symbol, we
173
+ # have not enforced this; warn, reject in lint mode
174
+ if Lint.enabled?
175
+ raise Error::LintError, "Auth mechanism #{@mechanism.inspect} must be specified as a symbol"
176
+ else
177
+ log_warn("Auth mechanism #{@mechanism.inspect} should be specified as a symbol")
178
+ @mechanism = @mechanism.to_sym
179
+ end
180
+ end
181
+ unless Auth::SOURCES.key?(@mechanism)
182
+ raise InvalidMechanism.new(options[:auth_mech])
183
+ end
184
+ end
143
185
  @auth_mech_properties = options[:auth_mech_properties] || {}
144
186
  @roles = options[:roles] || []
145
187
  @client_key = options[:client_key]
@@ -159,13 +201,6 @@ module Mongo
159
201
 
160
202
  private
161
203
 
162
- def sasl_prepped_password
163
- StringPrep.prepare(password,
164
- StringPrep::Profiles::SASL::MAPPINGS,
165
- StringPrep::Profiles::SASL::PROHIBITED,
166
- normalize: true, bidi: true)
167
- end
168
-
169
204
  # The client key for the user.
170
205
  #
171
206
  # @return [ String ] The client key for the user.