mongo 1.3.0 → 1.12.5

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 (185) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/{LICENSE.txt → LICENSE} +1 -1
  4. data/README.md +122 -271
  5. data/Rakefile +25 -209
  6. data/VERSION +1 -0
  7. data/bin/mongo_console +31 -9
  8. data/lib/mongo/bulk_write_collection_view.rb +387 -0
  9. data/lib/mongo/collection.rb +576 -269
  10. data/lib/mongo/collection_writer.rb +364 -0
  11. data/lib/mongo/connection/node.rb +249 -0
  12. data/lib/mongo/connection/pool.rb +340 -0
  13. data/lib/mongo/connection/pool_manager.rb +320 -0
  14. data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
  15. data/lib/mongo/connection/socket/socket_util.rb +37 -0
  16. data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
  17. data/lib/mongo/connection/socket/tcp_socket.rb +87 -0
  18. data/lib/mongo/connection/socket/unix_socket.rb +39 -0
  19. data/lib/mongo/connection/socket.rb +18 -0
  20. data/lib/mongo/connection.rb +7 -875
  21. data/lib/mongo/cursor.rb +403 -117
  22. data/lib/mongo/db.rb +444 -243
  23. data/lib/mongo/exception.rb +145 -0
  24. data/lib/mongo/functional/authentication.rb +455 -0
  25. data/lib/mongo/functional/logging.rb +85 -0
  26. data/lib/mongo/functional/read_preference.rb +183 -0
  27. data/lib/mongo/functional/scram.rb +556 -0
  28. data/lib/mongo/functional/uri_parser.rb +409 -0
  29. data/lib/mongo/functional/write_concern.rb +66 -0
  30. data/lib/mongo/functional.rb +20 -0
  31. data/lib/mongo/gridfs/grid.rb +30 -24
  32. data/lib/mongo/gridfs/grid_ext.rb +6 -10
  33. data/lib/mongo/gridfs/grid_file_system.rb +38 -20
  34. data/lib/mongo/gridfs/grid_io.rb +84 -75
  35. data/lib/mongo/gridfs.rb +18 -0
  36. data/lib/mongo/legacy.rb +140 -0
  37. data/lib/mongo/mongo_client.rb +697 -0
  38. data/lib/mongo/mongo_replica_set_client.rb +535 -0
  39. data/lib/mongo/mongo_sharded_client.rb +159 -0
  40. data/lib/mongo/networking.rb +372 -0
  41. data/lib/mongo/{util → utils}/conversions.rb +29 -8
  42. data/lib/mongo/{util → utils}/core_ext.rb +28 -18
  43. data/lib/mongo/{util → utils}/server_version.rb +4 -6
  44. data/lib/mongo/{util → utils}/support.rb +29 -31
  45. data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
  46. data/lib/mongo/utils.rb +19 -0
  47. data/lib/mongo.rb +51 -50
  48. data/mongo.gemspec +29 -32
  49. data/test/functional/authentication_test.rb +39 -0
  50. data/test/functional/bulk_api_stress_test.rb +133 -0
  51. data/test/functional/bulk_write_collection_view_test.rb +1198 -0
  52. data/test/functional/client_test.rb +627 -0
  53. data/test/functional/collection_test.rb +2175 -0
  54. data/test/functional/collection_writer_test.rb +83 -0
  55. data/test/{conversions_test.rb → functional/conversions_test.rb} +47 -3
  56. data/test/functional/cursor_fail_test.rb +57 -0
  57. data/test/functional/cursor_message_test.rb +56 -0
  58. data/test/functional/cursor_test.rb +683 -0
  59. data/test/functional/db_api_test.rb +835 -0
  60. data/test/functional/db_connection_test.rb +25 -0
  61. data/test/functional/db_test.rb +348 -0
  62. data/test/functional/grid_file_system_test.rb +285 -0
  63. data/test/{grid_io_test.rb → functional/grid_io_test.rb} +72 -11
  64. data/test/{grid_test.rb → functional/grid_test.rb} +88 -15
  65. data/test/functional/pool_test.rb +136 -0
  66. data/test/functional/safe_test.rb +98 -0
  67. data/test/functional/ssl_test.rb +29 -0
  68. data/test/functional/support_test.rb +62 -0
  69. data/test/functional/timeout_test.rb +60 -0
  70. data/test/functional/uri_test.rb +446 -0
  71. data/test/functional/write_concern_test.rb +118 -0
  72. data/test/helpers/general.rb +50 -0
  73. data/test/helpers/test_unit.rb +476 -0
  74. data/test/replica_set/authentication_test.rb +37 -0
  75. data/test/replica_set/basic_test.rb +189 -0
  76. data/test/replica_set/client_test.rb +393 -0
  77. data/test/replica_set/connection_test.rb +138 -0
  78. data/test/replica_set/count_test.rb +66 -0
  79. data/test/replica_set/cursor_test.rb +220 -0
  80. data/test/replica_set/insert_test.rb +157 -0
  81. data/test/replica_set/max_values_test.rb +151 -0
  82. data/test/replica_set/pinning_test.rb +105 -0
  83. data/test/replica_set/query_test.rb +73 -0
  84. data/test/replica_set/read_preference_test.rb +219 -0
  85. data/test/replica_set/refresh_test.rb +211 -0
  86. data/test/replica_set/replication_ack_test.rb +95 -0
  87. data/test/replica_set/ssl_test.rb +32 -0
  88. data/test/sharded_cluster/basic_test.rb +203 -0
  89. data/test/shared/authentication/basic_auth_shared.rb +260 -0
  90. data/test/shared/authentication/bulk_api_auth_shared.rb +249 -0
  91. data/test/shared/authentication/gssapi_shared.rb +176 -0
  92. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  93. data/test/shared/authentication/scram_shared.rb +92 -0
  94. data/test/shared/ssl_shared.rb +235 -0
  95. data/test/test_helper.rb +53 -94
  96. data/test/threading/basic_test.rb +120 -0
  97. data/test/tools/mongo_config.rb +708 -0
  98. data/test/tools/mongo_config_test.rb +160 -0
  99. data/test/unit/client_test.rb +381 -0
  100. data/test/unit/collection_test.rb +89 -53
  101. data/test/unit/connection_test.rb +282 -32
  102. data/test/unit/cursor_test.rb +206 -8
  103. data/test/unit/db_test.rb +55 -13
  104. data/test/unit/grid_test.rb +43 -16
  105. data/test/unit/mongo_sharded_client_test.rb +48 -0
  106. data/test/unit/node_test.rb +93 -0
  107. data/test/unit/pool_manager_test.rb +111 -0
  108. data/test/unit/read_pref_test.rb +406 -0
  109. data/test/unit/read_test.rb +159 -0
  110. data/test/unit/safe_test.rb +69 -36
  111. data/test/unit/sharding_pool_manager_test.rb +84 -0
  112. data/test/unit/write_concern_test.rb +175 -0
  113. data.tar.gz.sig +3 -0
  114. metadata +227 -216
  115. metadata.gz.sig +0 -0
  116. data/docs/CREDITS.md +0 -123
  117. data/docs/FAQ.md +0 -116
  118. data/docs/GridFS.md +0 -158
  119. data/docs/HISTORY.md +0 -244
  120. data/docs/RELEASES.md +0 -33
  121. data/docs/REPLICA_SETS.md +0 -72
  122. data/docs/TUTORIAL.md +0 -247
  123. data/docs/WRITE_CONCERN.md +0 -28
  124. data/lib/mongo/exceptions.rb +0 -71
  125. data/lib/mongo/gridfs/grid_io_fix.rb +0 -38
  126. data/lib/mongo/repl_set_connection.rb +0 -342
  127. data/lib/mongo/test.rb +0 -20
  128. data/lib/mongo/util/pool.rb +0 -177
  129. data/lib/mongo/util/uri_parser.rb +0 -185
  130. data/test/async/collection_test.rb +0 -224
  131. data/test/async/connection_test.rb +0 -24
  132. data/test/async/cursor_test.rb +0 -162
  133. data/test/async/worker_pool_test.rb +0 -99
  134. data/test/auxillary/1.4_features.rb +0 -166
  135. data/test/auxillary/authentication_test.rb +0 -68
  136. data/test/auxillary/autoreconnect_test.rb +0 -41
  137. data/test/auxillary/fork_test.rb +0 -30
  138. data/test/auxillary/repl_set_auth_test.rb +0 -58
  139. data/test/auxillary/slave_connection_test.rb +0 -36
  140. data/test/auxillary/threaded_authentication_test.rb +0 -101
  141. data/test/bson/binary_test.rb +0 -15
  142. data/test/bson/bson_test.rb +0 -649
  143. data/test/bson/byte_buffer_test.rb +0 -208
  144. data/test/bson/hash_with_indifferent_access_test.rb +0 -38
  145. data/test/bson/json_test.rb +0 -17
  146. data/test/bson/object_id_test.rb +0 -154
  147. data/test/bson/ordered_hash_test.rb +0 -204
  148. data/test/bson/timestamp_test.rb +0 -24
  149. data/test/collection_test.rb +0 -910
  150. data/test/connection_test.rb +0 -309
  151. data/test/cursor_fail_test.rb +0 -75
  152. data/test/cursor_message_test.rb +0 -43
  153. data/test/cursor_test.rb +0 -483
  154. data/test/db_api_test.rb +0 -726
  155. data/test/db_connection_test.rb +0 -15
  156. data/test/db_test.rb +0 -287
  157. data/test/grid_file_system_test.rb +0 -243
  158. data/test/load/resque/load.rb +0 -21
  159. data/test/load/resque/processor.rb +0 -26
  160. data/test/load/thin/load.rb +0 -24
  161. data/test/load/unicorn/load.rb +0 -23
  162. data/test/load/unicorn/unicorn.rb +0 -29
  163. data/test/replica_sets/connect_test.rb +0 -94
  164. data/test/replica_sets/connection_string_test.rb +0 -32
  165. data/test/replica_sets/count_test.rb +0 -35
  166. data/test/replica_sets/insert_test.rb +0 -53
  167. data/test/replica_sets/pooled_insert_test.rb +0 -55
  168. data/test/replica_sets/query_secondaries.rb +0 -96
  169. data/test/replica_sets/query_test.rb +0 -51
  170. data/test/replica_sets/replication_ack_test.rb +0 -66
  171. data/test/replica_sets/rs_test_helper.rb +0 -27
  172. data/test/safe_test.rb +0 -68
  173. data/test/support/hash_with_indifferent_access.rb +0 -186
  174. data/test/support/keys.rb +0 -45
  175. data/test/support_test.rb +0 -18
  176. data/test/threading/threading_with_large_pool_test.rb +0 -90
  177. data/test/threading_test.rb +0 -87
  178. data/test/tools/auth_repl_set_manager.rb +0 -14
  179. data/test/tools/load.rb +0 -58
  180. data/test/tools/repl_set_manager.rb +0 -266
  181. data/test/tools/sharding_manager.rb +0 -202
  182. data/test/tools/test.rb +0 -4
  183. data/test/unit/pool_test.rb +0 -9
  184. data/test/unit/repl_set_connection_test.rb +0 -59
  185. data/test/uri_test.rb +0 -91
@@ -0,0 +1,145 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ # Generic Mongo Ruby Driver exception class.
17
+ class MongoRubyError < StandardError; end
18
+
19
+ # Raised when MongoDB itself has returned an error.
20
+ class MongoDBError < RuntimeError
21
+
22
+ # @return The entire failed command's response object, if available.
23
+ attr_reader :result
24
+
25
+ # @return The failed command's error code, if availab.e
26
+ attr_reader :error_code
27
+
28
+ def initialize(message=nil, error_code=nil, result=nil)
29
+ @error_code = error_code
30
+ @result = result
31
+ super(message)
32
+ end
33
+ end
34
+
35
+ # Raised on fatal errors to GridFS.
36
+ class GridError < MongoRubyError; end
37
+
38
+ # Raised on fatal errors to GridFS.
39
+ class GridFileNotFound < GridError; end
40
+
41
+ # Raised on fatal errors to GridFS.
42
+ class GridMD5Failure < GridError; end
43
+
44
+ # Raised when invalid arguments are sent to Mongo Ruby methods.
45
+ class MongoArgumentError < MongoRubyError; end
46
+
47
+ # Raised on failures in connection to the database server.
48
+ class ConnectionError < MongoRubyError; end
49
+
50
+ # Raised on failures in connection to the database server.
51
+ class ReplicaSetConnectionError < ConnectionError; end
52
+
53
+ # Raised on failures in connection to the database server.
54
+ class ConnectionTimeoutError < MongoRubyError; end
55
+
56
+ # Raised when no tags in a read preference maps to a given connection.
57
+ class NodeWithTagsNotFound < MongoRubyError; end
58
+
59
+ # Raised when a connection operation fails.
60
+ class ConnectionFailure < MongoDBError; end
61
+
62
+ # Raised when authentication fails.
63
+ class AuthenticationError < MongoDBError; end
64
+
65
+ # Raised when a database operation fails.
66
+ class OperationFailure < MongoDBError; end
67
+
68
+ # Raised when a database operation exceeds maximum specified time.
69
+ class ExecutionTimeout < OperationFailure; end
70
+
71
+ # Raised when a database operation has a write concern error.
72
+ class WriteConcernError < OperationFailure; end
73
+
74
+ # Raised when a socket read operation times out.
75
+ class OperationTimeout < SocketError; end
76
+
77
+ # Raised when a client attempts to perform an invalid operation.
78
+ class InvalidOperation < MongoDBError; end
79
+
80
+ # Raised when an invalid collection or database name is used (invalid namespace name).
81
+ class InvalidNSName < RuntimeError; end
82
+
83
+ # Raised when the client supplies an invalid value to sort by.
84
+ class InvalidSortValueError < MongoRubyError; end
85
+
86
+ # Raised for bulk write errors.
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
145
+ end
@@ -0,0 +1,455 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'digest/md5'
16
+
17
+ module Mongo
18
+ module Authentication
19
+
20
+ DEFAULT_MECHANISM = 'MONGODB-CR'
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] }
26
+
27
+ # authentication module methods
28
+ class << self
29
+ # Helper to validate an authentication mechanism and optionally
30
+ # raise an error if invalid.
31
+ #
32
+ # @param mechanism [String] [description]
33
+ # @param raise_error [Boolean] [description]
34
+ #
35
+ # @raise [ArgumentError] if raise_error and not a valid auth mechanism.
36
+ # @return [Boolean] returns the validation result.
37
+ def validate_mechanism(mechanism, raise_error=false)
38
+ return true if MECHANISMS.include?(mechanism.upcase)
39
+ if raise_error
40
+ raise ArgumentError,
41
+ "Invalid authentication mechanism provided. Must be one of " +
42
+ "#{Mongo::Authentication::MECHANISMS.join(', ')}."
43
+ end
44
+ false
45
+ end
46
+
47
+
48
+ # Helper to validate and normalize credential sets.
49
+ #
50
+ # @param auth [Hash] A hash containing the credential set.
51
+ #
52
+ # @raise [MongoArgumentError] if the credential set is invalid.
53
+ # @return [Hash] The validated credential set.
54
+ def validate_credentials(auth)
55
+ # set the default auth source if not defined
56
+ auth[:source] = auth[:source] || auth[:db_name] || 'admin'
57
+
58
+ if password_required?(auth[:mechanism]) && !auth[:password]
59
+ raise MongoArgumentError,
60
+ "When using the authentication mechanism " +
61
+ "#{auth[:mechanism].nil? ? 'MONGODB-CR or SCRAM-SHA-1' : auth[:mechanism]} " +
62
+ "both username and password are required."
63
+ end
64
+ # if extra opts exist, validate them
65
+ allowed_keys = EXTRA[auth[:mechanism]]
66
+ if auth[:extra] && !auth[:extra].empty?
67
+ invalid_opts = []
68
+ auth[:extra].keys.each { |k| invalid_opts << k unless allowed_keys.include?(k) }
69
+ raise MongoArgumentError,
70
+ "Invalid extra option(s): #{invalid_opts} found. Please check the extra options" +
71
+ " passed and try again." unless invalid_opts.empty?
72
+ end
73
+ auth
74
+ end
75
+
76
+ # Generate an MD5 for authentication.
77
+ #
78
+ # @param username [String] The username.
79
+ # @param password [String] The user's password.
80
+ # @param nonce [String] The nonce value.
81
+ #
82
+ # @return [String] MD5 key for db authentication.
83
+ def auth_key(username, password, nonce)
84
+ Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
85
+ end
86
+
87
+ # Return a hashed password for auth.
88
+ #
89
+ # @param username [String] The username.
90
+ # @param password [String] The users's password.
91
+ #
92
+ # @return [String] The hashed password value.
93
+ def hash_password(username, password)
94
+ Digest::MD5.hexdigest("#{username}:mongo:#{password}")
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
109
+ end
110
+
111
+ # Saves a cache of authentication credentials to the current
112
+ # client instance. This method is called automatically by DB#authenticate.
113
+ #
114
+ # @param db_name [String] The current database name.
115
+ # @param username [String] The current username.
116
+ # @param password [String] (nil) The users's password (not required for
117
+ # all authentication mechanisms).
118
+ # @param source [String] (nil) The authentication source database
119
+ # (if different than the current database).
120
+ # @param mechanism [String] (nil) The authentication mechanism being used
121
+ # (default: 'MONGODB-CR' or 'SCRAM-SHA-1' if server version >= 2.7.8).
122
+ # @param extra [Hash] (nil) A optional hash of extra options to be stored with
123
+ # the credential set.
124
+ #
125
+ # @raise [MongoArgumentError] Raised if the database has already been used
126
+ # for authentication. A log out is required before additional auths can
127
+ # be issued against a given database.
128
+ # @raise [AuthenticationError] Raised if authentication fails.
129
+ # @return [Hash] a hash representing the authentication just added.
130
+ def add_auth(db_name, username, password=nil, source=nil, mechanism=nil, extra=nil)
131
+ auth = Authentication.validate_credentials({
132
+ :db_name => db_name,
133
+ :username => username,
134
+ :password => password,
135
+ :source => source,
136
+ :mechanism => mechanism,
137
+ :extra => extra
138
+ })
139
+
140
+ if @auths.any? {|a| a[:source] == auth[:source]}
141
+ raise MongoArgumentError,
142
+ "Another user has already authenticated to the database " +
143
+ "'#{auth[:source]}' and multiple authentications are not " +
144
+ "permitted. Please logout first."
145
+ end
146
+
147
+ begin
148
+ socket = checkout_reader(:mode => :primary_preferred)
149
+ issue_authentication(auth, :socket => socket)
150
+ ensure
151
+ socket.checkin if socket
152
+ end
153
+
154
+ @auths << auth
155
+ auth
156
+ end
157
+
158
+ # Remove a saved authentication for this connection.
159
+ #
160
+ # @param db_name [String] The database name.
161
+ #
162
+ # @return [Boolean] The result of the operation.
163
+ def remove_auth(db_name)
164
+ return false unless @auths
165
+ auths = @auths.to_a
166
+ removed = auths.reject! { |a| a[:source] == db_name }
167
+ @auths = Set.new(auths)
168
+ !!removed
169
+ end
170
+
171
+ # Remove all authentication information stored in this connection.
172
+ #
173
+ # @return [Boolean] result of the operation.
174
+ def clear_auths
175
+ @auths = Set.new
176
+ true
177
+ end
178
+
179
+ # Method to handle and issue logout commands.
180
+ #
181
+ # @note This method should not be called directly. Use DB#logout.
182
+ #
183
+ # @param db_name [String] The database name.
184
+ # @param opts [Hash] Hash of optional settings and configuration values.
185
+ #
186
+ # @option opts [Socket] socket Socket instance to use.
187
+ #
188
+ # @raise [MongoDBError] Raised if the logout operation fails.
189
+ # @return [Boolean] The result of the logout operation.
190
+ def issue_logout(db_name, opts={})
191
+ doc = auth_command({:logout => 1}, opts[:socket], db_name).first
192
+ unless Support.ok?(doc)
193
+ raise MongoDBError, "Error logging out on DB #{db_name}."
194
+ end
195
+ true # somewhat pointless, but here to preserve the existing API
196
+ end
197
+
198
+ # Method to handle and issue authentication commands.
199
+ #
200
+ # @note This method should not be called directly. Use DB#authenticate.
201
+ #
202
+ # @param auth [Hash] The authentication credentials to be used.
203
+ # @param opts [Hash] Hash of optional settings and configuration values.
204
+ #
205
+ # @option opts [Socket] socket Socket instance to use.
206
+ #
207
+ # @raise [AuthenticationError] Raised if the authentication fails.
208
+ # @return [Boolean] Result of the authentication operation.
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])
215
+ result = case auth[:mechanism]
216
+ when 'MONGODB-CR'
217
+ issue_cr(auth, opts)
218
+ when 'MONGODB-X509'
219
+ issue_x509(auth, opts)
220
+ when 'PLAIN'
221
+ issue_plain(auth, opts)
222
+ when 'GSSAPI'
223
+ issue_gssapi(auth, opts)
224
+ when 'SCRAM-SHA-1'
225
+ issue_scram(auth, opts)
226
+ end
227
+
228
+ unless Support.ok?(result)
229
+ raise AuthenticationError,
230
+ "Failed to authenticate user '#{auth[:username]}' " +
231
+ "on db '#{auth[:source]}'."
232
+ end
233
+
234
+ true
235
+ end
236
+
237
+ private
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
+
319
+ # Handles issuing authentication commands for the MONGODB-CR auth mechanism.
320
+ #
321
+ # @param auth [Hash] The authentication credentials to be used.
322
+ # @param opts [Hash] Hash of optional settings and configuration values.
323
+ #
324
+ # @option opts [Socket] socket Socket instance to use.
325
+ #
326
+ # @return [Boolean] Result of the authentication operation.
327
+ #
328
+ # @private
329
+ def issue_cr(auth, opts={})
330
+ db_name = auth[:source]
331
+ nonce = get_nonce(auth[:source], opts)
332
+
333
+ # build auth command document
334
+ cmd = BSON::OrderedHash.new
335
+ cmd['authenticate'] = 1
336
+ cmd['user'] = auth[:username]
337
+ cmd['nonce'] = nonce
338
+ cmd['key'] = Authentication.auth_key(auth[:username],
339
+ auth[:password],
340
+ nonce)
341
+ auth_command(cmd, opts[:socket], db_name).first
342
+ end
343
+
344
+ # Handles issuing authentication commands for the MONGODB-X509 auth mechanism.
345
+ #
346
+ # @param auth [Hash] The authentication credentials to be used.
347
+ # @param opts [Hash] Hash of optional settings and configuration values.
348
+ #
349
+ # @private
350
+ def issue_x509(auth, opts={})
351
+ db_name = '$external'
352
+
353
+ cmd = BSON::OrderedHash.new
354
+ cmd[:authenticate] = 1
355
+ cmd[:mechanism] = auth[:mechanism]
356
+ cmd[:user] = auth[:username]
357
+
358
+ auth_command(cmd, opts[:socket], db_name).first
359
+ end
360
+
361
+ # Handles issuing authentication commands for the PLAIN auth mechanism.
362
+ #
363
+ # @param auth [Hash] The authentication credentials to be used.
364
+ # @param opts [Hash] Hash of optional settings and configuration values.
365
+ #
366
+ # @option opts [Socket] socket Socket instance to use.
367
+ #
368
+ # @return [Boolean] Result of the authentication operation.
369
+ #
370
+ # @private
371
+ def issue_plain(auth, opts={})
372
+ db_name = auth[:source]
373
+ payload = "\x00#{auth[:username]}\x00#{auth[:password]}"
374
+
375
+ cmd = BSON::OrderedHash.new
376
+ cmd[:saslStart] = 1
377
+ cmd[:mechanism] = auth[:mechanism]
378
+ cmd[:payload] = BSON::Binary.new(payload)
379
+ cmd[:autoAuthorize] = 1
380
+
381
+ auth_command(cmd, opts[:socket], db_name).first
382
+ end
383
+
384
+ # Handles issuing authentication commands for the GSSAPI auth mechanism.
385
+ #
386
+ # @param auth [Hash] The authentication credentials to be used.
387
+ # @param opts [Hash] Hash of optional settings and configuration values.
388
+ #
389
+ # @private
390
+ def issue_gssapi(auth, opts={})
391
+ raise "In order to use Kerberos, please add the mongo-kerberos gem to your dependencies"
392
+ end
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
+
417
+ # Helper to fetch a nonce value from a given database instance.
418
+ #
419
+ # @param database [Mongo::DB] The DB instance to use for issue the nonce command.
420
+ # @param opts [Hash] Hash of optional settings and configuration values.
421
+ #
422
+ # @option opts [Socket] socket Socket instance to use.
423
+ #
424
+ # @raise [MongoDBError] Raised if there is an error executing the command.
425
+ # @return [String] Returns the nonce value.
426
+ #
427
+ # @private
428
+ def get_nonce(db_name, opts={})
429
+ cmd = BSON::OrderedHash.new
430
+ cmd[:getnonce] = 1
431
+ doc = auth_command(cmd, opts[:socket], db_name).first
432
+
433
+ unless Support.ok?(doc)
434
+ raise MongoDBError, "Error retrieving nonce: #{doc}"
435
+ end
436
+ doc['nonce']
437
+ end
438
+
439
+ def auth_command(selector, socket, db_name)
440
+ begin
441
+ message = build_command_message(db_name, selector)
442
+ request_id = add_message_headers(message, Mongo::Constants::OP_QUERY)
443
+ packed_message = message.to_s
444
+
445
+ send_message_on_socket(packed_message, socket)
446
+ receive(socket, request_id).shift
447
+ rescue OperationFailure => ex
448
+ return ex.result
449
+ rescue ConnectionFailure, OperationTimeout => ex
450
+ socket.close
451
+ raise ex
452
+ end
453
+ end
454
+ end
455
+ end
@@ -0,0 +1,85 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ module Logging
17
+
18
+ module Instrumenter
19
+ def self.instrument(name, payload = {})
20
+ yield
21
+ end
22
+ end
23
+
24
+ @instrumenter = Instrumenter
25
+
26
+ def write_logging_startup_message
27
+ log(:debug, "Logging level is currently :debug which could negatively impact " +
28
+ "client-side performance. You should set your logging level no lower than " +
29
+ ":info in production.")
30
+ end
31
+
32
+ # Log a message with the given level.
33
+ def log(level, msg)
34
+ return unless @logger
35
+ case level
36
+ when :fatal then
37
+ @logger.fatal "MONGODB [FATAL] #{msg}"
38
+ when :error then
39
+ @logger.error "MONGODB [ERROR] #{msg}"
40
+ when :warn then
41
+ @logger.warn "MONGODB [WARNING] #{msg}"
42
+ when :info then
43
+ @logger.info "MONGODB [INFO] #{msg}"
44
+ when :debug then
45
+ @logger.debug "MONGODB [DEBUG] #{msg}"
46
+ else
47
+ @logger.debug "MONGODB [DEBUG] #{msg}"
48
+ end
49
+ end
50
+
51
+ # Execute the block and log the operation described by name and payload.
52
+ def instrument(name, payload = {})
53
+ start_time = Time.now
54
+ res = Logging.instrumenter.instrument(name, payload) do
55
+ yield
56
+ end
57
+ duration = Time.now - start_time
58
+ log_operation(name, payload, duration)
59
+ res
60
+ end
61
+
62
+ def self.instrumenter
63
+ @instrumenter
64
+ end
65
+
66
+ def self.instrumenter=(instrumenter)
67
+ @instrumenter = instrumenter
68
+ end
69
+
70
+ protected
71
+
72
+ def log_operation(name, payload, duration)
73
+ @logger && @logger.debug do
74
+ msg = "MONGODB "
75
+ msg << "(%.1fms) " % (duration * 1000)
76
+ msg << "#{payload[:database]}['#{payload[:collection]}'].#{name}("
77
+ msg << payload.values_at(:selector, :document, :documents, :fields ).compact.map(&:inspect).join(', ') + ")"
78
+ msg << ".skip(#{payload[:skip]})" if payload[:skip]
79
+ msg << ".limit(#{payload[:limit]})" if payload[:limit]
80
+ msg << ".sort(#{payload[:order]})" if payload[:order]
81
+ msg
82
+ end
83
+ end
84
+ end
85
+ end