mongo 2.7.2 → 2.8.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 (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.