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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +122 -271
- data/Rakefile +25 -209
- data/VERSION +1 -0
- data/bin/mongo_console +31 -9
- data/lib/mongo/bulk_write_collection_view.rb +387 -0
- data/lib/mongo/collection.rb +576 -269
- data/lib/mongo/collection_writer.rb +364 -0
- data/lib/mongo/connection/node.rb +249 -0
- data/lib/mongo/connection/pool.rb +340 -0
- data/lib/mongo/connection/pool_manager.rb +320 -0
- data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
- data/lib/mongo/connection/socket/socket_util.rb +37 -0
- data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
- data/lib/mongo/connection/socket/tcp_socket.rb +87 -0
- data/lib/mongo/connection/socket/unix_socket.rb +39 -0
- data/lib/mongo/connection/socket.rb +18 -0
- data/lib/mongo/connection.rb +7 -875
- data/lib/mongo/cursor.rb +403 -117
- data/lib/mongo/db.rb +444 -243
- data/lib/mongo/exception.rb +145 -0
- data/lib/mongo/functional/authentication.rb +455 -0
- data/lib/mongo/functional/logging.rb +85 -0
- data/lib/mongo/functional/read_preference.rb +183 -0
- data/lib/mongo/functional/scram.rb +556 -0
- data/lib/mongo/functional/uri_parser.rb +409 -0
- data/lib/mongo/functional/write_concern.rb +66 -0
- data/lib/mongo/functional.rb +20 -0
- data/lib/mongo/gridfs/grid.rb +30 -24
- data/lib/mongo/gridfs/grid_ext.rb +6 -10
- data/lib/mongo/gridfs/grid_file_system.rb +38 -20
- data/lib/mongo/gridfs/grid_io.rb +84 -75
- data/lib/mongo/gridfs.rb +18 -0
- data/lib/mongo/legacy.rb +140 -0
- data/lib/mongo/mongo_client.rb +697 -0
- data/lib/mongo/mongo_replica_set_client.rb +535 -0
- data/lib/mongo/mongo_sharded_client.rb +159 -0
- data/lib/mongo/networking.rb +372 -0
- data/lib/mongo/{util → utils}/conversions.rb +29 -8
- data/lib/mongo/{util → utils}/core_ext.rb +28 -18
- data/lib/mongo/{util → utils}/server_version.rb +4 -6
- data/lib/mongo/{util → utils}/support.rb +29 -31
- data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
- data/lib/mongo/utils.rb +19 -0
- data/lib/mongo.rb +51 -50
- data/mongo.gemspec +29 -32
- data/test/functional/authentication_test.rb +39 -0
- data/test/functional/bulk_api_stress_test.rb +133 -0
- data/test/functional/bulk_write_collection_view_test.rb +1198 -0
- data/test/functional/client_test.rb +627 -0
- data/test/functional/collection_test.rb +2175 -0
- data/test/functional/collection_writer_test.rb +83 -0
- data/test/{conversions_test.rb → functional/conversions_test.rb} +47 -3
- data/test/functional/cursor_fail_test.rb +57 -0
- data/test/functional/cursor_message_test.rb +56 -0
- data/test/functional/cursor_test.rb +683 -0
- data/test/functional/db_api_test.rb +835 -0
- data/test/functional/db_connection_test.rb +25 -0
- data/test/functional/db_test.rb +348 -0
- data/test/functional/grid_file_system_test.rb +285 -0
- data/test/{grid_io_test.rb → functional/grid_io_test.rb} +72 -11
- data/test/{grid_test.rb → functional/grid_test.rb} +88 -15
- data/test/functional/pool_test.rb +136 -0
- data/test/functional/safe_test.rb +98 -0
- data/test/functional/ssl_test.rb +29 -0
- data/test/functional/support_test.rb +62 -0
- data/test/functional/timeout_test.rb +60 -0
- data/test/functional/uri_test.rb +446 -0
- data/test/functional/write_concern_test.rb +118 -0
- data/test/helpers/general.rb +50 -0
- data/test/helpers/test_unit.rb +476 -0
- data/test/replica_set/authentication_test.rb +37 -0
- data/test/replica_set/basic_test.rb +189 -0
- data/test/replica_set/client_test.rb +393 -0
- data/test/replica_set/connection_test.rb +138 -0
- data/test/replica_set/count_test.rb +66 -0
- data/test/replica_set/cursor_test.rb +220 -0
- data/test/replica_set/insert_test.rb +157 -0
- data/test/replica_set/max_values_test.rb +151 -0
- data/test/replica_set/pinning_test.rb +105 -0
- data/test/replica_set/query_test.rb +73 -0
- data/test/replica_set/read_preference_test.rb +219 -0
- data/test/replica_set/refresh_test.rb +211 -0
- data/test/replica_set/replication_ack_test.rb +95 -0
- data/test/replica_set/ssl_test.rb +32 -0
- data/test/sharded_cluster/basic_test.rb +203 -0
- data/test/shared/authentication/basic_auth_shared.rb +260 -0
- data/test/shared/authentication/bulk_api_auth_shared.rb +249 -0
- data/test/shared/authentication/gssapi_shared.rb +176 -0
- data/test/shared/authentication/sasl_plain_shared.rb +96 -0
- data/test/shared/authentication/scram_shared.rb +92 -0
- data/test/shared/ssl_shared.rb +235 -0
- data/test/test_helper.rb +53 -94
- data/test/threading/basic_test.rb +120 -0
- data/test/tools/mongo_config.rb +708 -0
- data/test/tools/mongo_config_test.rb +160 -0
- data/test/unit/client_test.rb +381 -0
- data/test/unit/collection_test.rb +89 -53
- data/test/unit/connection_test.rb +282 -32
- data/test/unit/cursor_test.rb +206 -8
- data/test/unit/db_test.rb +55 -13
- data/test/unit/grid_test.rb +43 -16
- data/test/unit/mongo_sharded_client_test.rb +48 -0
- data/test/unit/node_test.rb +93 -0
- data/test/unit/pool_manager_test.rb +111 -0
- data/test/unit/read_pref_test.rb +406 -0
- data/test/unit/read_test.rb +159 -0
- data/test/unit/safe_test.rb +69 -36
- data/test/unit/sharding_pool_manager_test.rb +84 -0
- data/test/unit/write_concern_test.rb +175 -0
- data.tar.gz.sig +3 -0
- metadata +227 -216
- metadata.gz.sig +0 -0
- data/docs/CREDITS.md +0 -123
- data/docs/FAQ.md +0 -116
- data/docs/GridFS.md +0 -158
- data/docs/HISTORY.md +0 -244
- data/docs/RELEASES.md +0 -33
- data/docs/REPLICA_SETS.md +0 -72
- data/docs/TUTORIAL.md +0 -247
- data/docs/WRITE_CONCERN.md +0 -28
- data/lib/mongo/exceptions.rb +0 -71
- data/lib/mongo/gridfs/grid_io_fix.rb +0 -38
- data/lib/mongo/repl_set_connection.rb +0 -342
- data/lib/mongo/test.rb +0 -20
- data/lib/mongo/util/pool.rb +0 -177
- data/lib/mongo/util/uri_parser.rb +0 -185
- data/test/async/collection_test.rb +0 -224
- data/test/async/connection_test.rb +0 -24
- data/test/async/cursor_test.rb +0 -162
- data/test/async/worker_pool_test.rb +0 -99
- data/test/auxillary/1.4_features.rb +0 -166
- data/test/auxillary/authentication_test.rb +0 -68
- data/test/auxillary/autoreconnect_test.rb +0 -41
- data/test/auxillary/fork_test.rb +0 -30
- data/test/auxillary/repl_set_auth_test.rb +0 -58
- data/test/auxillary/slave_connection_test.rb +0 -36
- data/test/auxillary/threaded_authentication_test.rb +0 -101
- data/test/bson/binary_test.rb +0 -15
- data/test/bson/bson_test.rb +0 -649
- data/test/bson/byte_buffer_test.rb +0 -208
- data/test/bson/hash_with_indifferent_access_test.rb +0 -38
- data/test/bson/json_test.rb +0 -17
- data/test/bson/object_id_test.rb +0 -154
- data/test/bson/ordered_hash_test.rb +0 -204
- data/test/bson/timestamp_test.rb +0 -24
- data/test/collection_test.rb +0 -910
- data/test/connection_test.rb +0 -309
- data/test/cursor_fail_test.rb +0 -75
- data/test/cursor_message_test.rb +0 -43
- data/test/cursor_test.rb +0 -483
- data/test/db_api_test.rb +0 -726
- data/test/db_connection_test.rb +0 -15
- data/test/db_test.rb +0 -287
- data/test/grid_file_system_test.rb +0 -243
- data/test/load/resque/load.rb +0 -21
- data/test/load/resque/processor.rb +0 -26
- data/test/load/thin/load.rb +0 -24
- data/test/load/unicorn/load.rb +0 -23
- data/test/load/unicorn/unicorn.rb +0 -29
- data/test/replica_sets/connect_test.rb +0 -94
- data/test/replica_sets/connection_string_test.rb +0 -32
- data/test/replica_sets/count_test.rb +0 -35
- data/test/replica_sets/insert_test.rb +0 -53
- data/test/replica_sets/pooled_insert_test.rb +0 -55
- data/test/replica_sets/query_secondaries.rb +0 -96
- data/test/replica_sets/query_test.rb +0 -51
- data/test/replica_sets/replication_ack_test.rb +0 -66
- data/test/replica_sets/rs_test_helper.rb +0 -27
- data/test/safe_test.rb +0 -68
- data/test/support/hash_with_indifferent_access.rb +0 -186
- data/test/support/keys.rb +0 -45
- data/test/support_test.rb +0 -18
- data/test/threading/threading_with_large_pool_test.rb +0 -90
- data/test/threading_test.rb +0 -87
- data/test/tools/auth_repl_set_manager.rb +0 -14
- data/test/tools/load.rb +0 -58
- data/test/tools/repl_set_manager.rb +0 -266
- data/test/tools/sharding_manager.rb +0 -202
- data/test/tools/test.rb +0 -4
- data/test/unit/pool_test.rb +0 -9
- data/test/unit/repl_set_connection_test.rb +0 -59
- data/test/uri_test.rb +0 -91
@@ -0,0 +1,556 @@
|
|
1
|
+
# Copyright (C) 2014 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 'base64'
|
16
|
+
require 'securerandom'
|
17
|
+
require 'openssl'
|
18
|
+
require 'digest/md5'
|
19
|
+
|
20
|
+
module Mongo
|
21
|
+
module Authentication
|
22
|
+
|
23
|
+
# Defines behaviour around a single SCRAM-SHA-1 conversation between the
|
24
|
+
# client and server.
|
25
|
+
#
|
26
|
+
# @since 1.12.0
|
27
|
+
class SCRAM
|
28
|
+
|
29
|
+
# The client key string.
|
30
|
+
#
|
31
|
+
# @since 1.12.0
|
32
|
+
CLIENT_KEY = 'Client Key'.freeze
|
33
|
+
|
34
|
+
# The digest to use for encryption.
|
35
|
+
#
|
36
|
+
# @since 1.12.0
|
37
|
+
DIGEST = OpenSSL::Digest::SHA1.new.freeze
|
38
|
+
|
39
|
+
# The key for the done field in the responses.
|
40
|
+
#
|
41
|
+
# @since 1.12.0
|
42
|
+
DONE = 'done'.freeze
|
43
|
+
|
44
|
+
# The conversation id field.
|
45
|
+
#
|
46
|
+
# @since 1.12.0
|
47
|
+
ID = 'conversationId'.freeze
|
48
|
+
|
49
|
+
# The iterations key in the responses.
|
50
|
+
#
|
51
|
+
# @since 1.12.0
|
52
|
+
ITERATIONS = /i=(\d+)/.freeze
|
53
|
+
|
54
|
+
# The payload field.
|
55
|
+
#
|
56
|
+
# @since 1.12.0
|
57
|
+
PAYLOAD = 'payload'.freeze
|
58
|
+
|
59
|
+
# The rnonce key in the responses.
|
60
|
+
#
|
61
|
+
# @since 1.12.0
|
62
|
+
RNONCE = /r=([^,]*)/.freeze
|
63
|
+
|
64
|
+
# The salt key in the responses.
|
65
|
+
#
|
66
|
+
# @since 1.12.0
|
67
|
+
SALT = /s=([^,]*)/.freeze
|
68
|
+
|
69
|
+
# The server key string.
|
70
|
+
#
|
71
|
+
# @since 1.12.0
|
72
|
+
SERVER_KEY = 'Server Key'.freeze
|
73
|
+
|
74
|
+
# The server signature verifier in the response.
|
75
|
+
#
|
76
|
+
# @since 1.12.0
|
77
|
+
VERIFIER = /v=([^,]*)/.freeze
|
78
|
+
|
79
|
+
# @return [ String ] nonce The initial user nonce.
|
80
|
+
attr_reader :nonce
|
81
|
+
|
82
|
+
# @return [ BSON::OrderedHash ] reply The current reply in the conversation.
|
83
|
+
attr_reader :reply
|
84
|
+
|
85
|
+
# @return [ Hash ] auth The authentication details.
|
86
|
+
attr_reader :auth
|
87
|
+
|
88
|
+
# @return [ String ] hashed_password The user's hashed password
|
89
|
+
attr_reader :hashed_password
|
90
|
+
|
91
|
+
# Continue the SCRAM conversation. This sends the client final message
|
92
|
+
# to the server after setting the reply from the previous server
|
93
|
+
# communication.
|
94
|
+
#
|
95
|
+
# @example Continue the conversation.
|
96
|
+
# conversation.continue(reply)
|
97
|
+
#
|
98
|
+
# @param [ BSON::OrderedHash ] reply The reply of the previous
|
99
|
+
# message.
|
100
|
+
#
|
101
|
+
# @return [ BSON::OrderedHash ] The next message to send.
|
102
|
+
#
|
103
|
+
# @since 1.12.0
|
104
|
+
def continue(reply)
|
105
|
+
validate_first_message!(reply)
|
106
|
+
command = BSON::OrderedHash.new
|
107
|
+
command['saslContinue'] = 1
|
108
|
+
command[PAYLOAD] = client_final_message
|
109
|
+
command[ID] = id
|
110
|
+
command
|
111
|
+
end
|
112
|
+
|
113
|
+
# Continue the SCRAM conversation for copydb. This sends the client final message
|
114
|
+
# to the server after setting the reply from the previous server
|
115
|
+
# communication.
|
116
|
+
#
|
117
|
+
# @example Continue the conversation when copying a database.
|
118
|
+
# conversation.copy_db_continue(reply)
|
119
|
+
#
|
120
|
+
# @param [ BSON::OrderedHash ] reply The reply of the previous
|
121
|
+
# message.
|
122
|
+
#
|
123
|
+
# @return [ BSON::OrderedHash ] The next message to send.
|
124
|
+
#
|
125
|
+
# @since 1.12.0
|
126
|
+
def copy_db_continue(reply)
|
127
|
+
validate_first_message!(reply)
|
128
|
+
command = BSON::OrderedHash.new
|
129
|
+
command['copydb'] = 1
|
130
|
+
command['fromhost'] = @copy_db[:from_host]
|
131
|
+
command['fromdb'] = @copy_db[:from_db]
|
132
|
+
command['todb'] = @copy_db[:to_db]
|
133
|
+
command[PAYLOAD] = client_final_message
|
134
|
+
command[ID] = id
|
135
|
+
command
|
136
|
+
end
|
137
|
+
|
138
|
+
# Finalize the SCRAM conversation. This is meant to be iterated until
|
139
|
+
# the provided reply indicates the conversation is finished.
|
140
|
+
#
|
141
|
+
# @example Finalize the conversation.
|
142
|
+
# conversation.finalize(reply)
|
143
|
+
#
|
144
|
+
# @param [ BSON::OrderedHash ] reply The reply of the previous
|
145
|
+
# message.
|
146
|
+
#
|
147
|
+
# @return [ BSON::OrderedHash ] The next message to send.
|
148
|
+
#
|
149
|
+
# @since 1.12.0
|
150
|
+
def finalize(reply)
|
151
|
+
validate_final_message!(reply)
|
152
|
+
command = BSON::OrderedHash.new
|
153
|
+
command['saslContinue'] = 1
|
154
|
+
command[PAYLOAD] = client_empty_message
|
155
|
+
command[ID] = id
|
156
|
+
command
|
157
|
+
end
|
158
|
+
|
159
|
+
# Start the SCRAM conversation. This returns the first message that
|
160
|
+
# needs to be send to the server.
|
161
|
+
#
|
162
|
+
# @example Start the conversation.
|
163
|
+
# conversation.start
|
164
|
+
#
|
165
|
+
# @return [ BSON::OrderedHash ] The first SCRAM conversation message.
|
166
|
+
#
|
167
|
+
# @since 1.12.0
|
168
|
+
def start
|
169
|
+
command = BSON::OrderedHash.new
|
170
|
+
command['saslStart'] = 1
|
171
|
+
command['autoAuthorize'] = 1
|
172
|
+
command[PAYLOAD] = client_first_message
|
173
|
+
command['mechanism'] = 'SCRAM-SHA-1'
|
174
|
+
command
|
175
|
+
end
|
176
|
+
|
177
|
+
# Start the SCRAM conversation for copying a database.
|
178
|
+
# This returns the first message that needs to be sent to the server.
|
179
|
+
#
|
180
|
+
# @example Start the copydb conversation.
|
181
|
+
# conversation.copy_db_start
|
182
|
+
#
|
183
|
+
# @return [ BSON::OrderedHash ] The first SCRAM copy_db conversation message.
|
184
|
+
#
|
185
|
+
# @since 1.12.0
|
186
|
+
def copy_db_start
|
187
|
+
command = BSON::OrderedHash.new
|
188
|
+
command['copydbsaslstart'] = 1
|
189
|
+
command['autoAuthorize'] = 1
|
190
|
+
command['fromhost'] = @copy_db[:from_host]
|
191
|
+
command['fromdb'] = @copy_db[:from_db]
|
192
|
+
command[PAYLOAD] = client_first_message
|
193
|
+
command['mechanism'] = 'SCRAM-SHA-1'
|
194
|
+
command
|
195
|
+
end
|
196
|
+
|
197
|
+
# Get the id of the conversation.
|
198
|
+
#
|
199
|
+
# @example Get the id of the conversation.
|
200
|
+
# conversation.id
|
201
|
+
#
|
202
|
+
# @return [ Integer ] The conversation id.
|
203
|
+
#
|
204
|
+
# @since 1.12.0
|
205
|
+
def id
|
206
|
+
reply[ID]
|
207
|
+
end
|
208
|
+
|
209
|
+
# Create the new conversation.
|
210
|
+
#
|
211
|
+
# @example Create the new conversation.
|
212
|
+
# Conversation.new(auth, password)
|
213
|
+
#
|
214
|
+
# @since 1.12.0
|
215
|
+
def initialize(auth, hashed_password, opts={})
|
216
|
+
@auth = auth
|
217
|
+
@hashed_password = hashed_password
|
218
|
+
@nonce = SecureRandom.base64
|
219
|
+
@copy_db = opts[:copy_db] if opts[:copy_db]
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
# Auth message algorithm implementation.
|
225
|
+
#
|
226
|
+
# @api private
|
227
|
+
#
|
228
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
229
|
+
#
|
230
|
+
# @since 1.12.0
|
231
|
+
def auth_message
|
232
|
+
@auth_message ||= "#{first_bare},#{payload_data},#{without_proof}"
|
233
|
+
end
|
234
|
+
|
235
|
+
# Get the empty client message.
|
236
|
+
#
|
237
|
+
# @api private
|
238
|
+
#
|
239
|
+
# @since 1.12.0
|
240
|
+
def client_empty_message
|
241
|
+
BSON::Binary.new('')
|
242
|
+
end
|
243
|
+
|
244
|
+
# Get the final client message.
|
245
|
+
#
|
246
|
+
# @api private
|
247
|
+
#
|
248
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
249
|
+
#
|
250
|
+
# @since 1.12.0
|
251
|
+
def client_final_message
|
252
|
+
BSON::Binary.new("#{without_proof},p=#{client_final}")
|
253
|
+
end
|
254
|
+
|
255
|
+
# Get the client first message
|
256
|
+
#
|
257
|
+
# @api private
|
258
|
+
#
|
259
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
260
|
+
#
|
261
|
+
# @since 1.12.0
|
262
|
+
def client_first_message
|
263
|
+
BSON::Binary.new("n,,#{first_bare}")
|
264
|
+
end
|
265
|
+
|
266
|
+
# Client final implementation.
|
267
|
+
#
|
268
|
+
# @api private
|
269
|
+
#
|
270
|
+
# @see http://tools.ietf.org/html/rfc5802#section-7
|
271
|
+
#
|
272
|
+
# @since 1.12.0
|
273
|
+
def client_final
|
274
|
+
@client_final ||= client_proof(client_key, client_signature(stored_key(client_key), auth_message))
|
275
|
+
end
|
276
|
+
|
277
|
+
# Client key algorithm implementation.
|
278
|
+
#
|
279
|
+
# @api private
|
280
|
+
#
|
281
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
282
|
+
#
|
283
|
+
# @since 1.12.0
|
284
|
+
def client_key
|
285
|
+
@client_key ||= hmac(salted_password, CLIENT_KEY)
|
286
|
+
end
|
287
|
+
|
288
|
+
if Base64.respond_to?(:strict_encode64)
|
289
|
+
|
290
|
+
# Client proof algorithm implementation.
|
291
|
+
#
|
292
|
+
# @api private
|
293
|
+
#
|
294
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
295
|
+
#
|
296
|
+
# @since 1.12.0
|
297
|
+
def client_proof(key, signature)
|
298
|
+
@client_proof ||= Base64.strict_encode64(xor(key, signature))
|
299
|
+
end
|
300
|
+
else
|
301
|
+
|
302
|
+
# Client proof algorithm implementation.
|
303
|
+
#
|
304
|
+
# @api private
|
305
|
+
#
|
306
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
307
|
+
#
|
308
|
+
# @since 1.12.0
|
309
|
+
def client_proof(key, signature)
|
310
|
+
@client_proof ||= Base64.encode64(xor(key, signature)).gsub("\n",'')
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Client signature algorithm implementation.
|
315
|
+
#
|
316
|
+
# @api private
|
317
|
+
#
|
318
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
319
|
+
#
|
320
|
+
# @since 1.12.0
|
321
|
+
def client_signature(key, message)
|
322
|
+
@client_signature ||= hmac(key, message)
|
323
|
+
end
|
324
|
+
|
325
|
+
if Base64.respond_to?(:strict_decode64)
|
326
|
+
|
327
|
+
# Get the base 64 decoded salt.
|
328
|
+
#
|
329
|
+
# @api private
|
330
|
+
#
|
331
|
+
# @since 1.12.0
|
332
|
+
def decoded_salt
|
333
|
+
@decoded_salt ||= Base64.strict_decode64(salt)
|
334
|
+
end
|
335
|
+
else
|
336
|
+
|
337
|
+
# Get the base 64 decoded salt.
|
338
|
+
#
|
339
|
+
# @api private
|
340
|
+
#
|
341
|
+
# @since 1.12.0
|
342
|
+
def decoded_salt
|
343
|
+
@decoded_salt ||= Base64.decode64(salt)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# First bare implementation.
|
348
|
+
#
|
349
|
+
# @api private
|
350
|
+
#
|
351
|
+
# @see http://tools.ietf.org/html/rfc5802#section-7
|
352
|
+
#
|
353
|
+
# @since 1.12.0
|
354
|
+
def first_bare
|
355
|
+
@first_bare ||= "n=#{auth[:username].gsub('=','=3D').gsub(',','=2C')},r=#{nonce}"
|
356
|
+
end
|
357
|
+
|
358
|
+
# H algorithm implementation.
|
359
|
+
#
|
360
|
+
# @api private
|
361
|
+
#
|
362
|
+
# @see http://tools.ietf.org/html/rfc5802#section-2.2
|
363
|
+
#
|
364
|
+
# @since 1.12.0
|
365
|
+
def h(string)
|
366
|
+
DIGEST.digest(string)
|
367
|
+
end
|
368
|
+
|
369
|
+
if defined?(OpenSSL::PKCS5)
|
370
|
+
|
371
|
+
# HI algorithm implementation.
|
372
|
+
#
|
373
|
+
# @api private
|
374
|
+
#
|
375
|
+
# @see http://tools.ietf.org/html/rfc5802#section-2.2
|
376
|
+
#
|
377
|
+
# @since 1.12.0
|
378
|
+
def hi(data)
|
379
|
+
OpenSSL::PKCS5.pbkdf2_hmac_sha1(data, decoded_salt, iterations, DIGEST.size)
|
380
|
+
end
|
381
|
+
else
|
382
|
+
|
383
|
+
# HI algorithm implementation.
|
384
|
+
#
|
385
|
+
# @api private
|
386
|
+
#
|
387
|
+
# @see http://tools.ietf.org/html/rfc5802#section-2.2
|
388
|
+
#
|
389
|
+
# @since 1.12.0
|
390
|
+
def hi(data)
|
391
|
+
u = hmac(data, decoded_salt + [1].pack("N"))
|
392
|
+
v = u
|
393
|
+
2.upto(iterations) do |i|
|
394
|
+
u = hmac(data, u)
|
395
|
+
v = xor(v, u)
|
396
|
+
end
|
397
|
+
v
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# HMAC algorithm implementation.
|
402
|
+
#
|
403
|
+
# @api private
|
404
|
+
#
|
405
|
+
# @see http://tools.ietf.org/html/rfc5802#section-2.2
|
406
|
+
#
|
407
|
+
# @since 1.12.0
|
408
|
+
def hmac(data, key)
|
409
|
+
OpenSSL::HMAC.digest(DIGEST, data, key)
|
410
|
+
end
|
411
|
+
|
412
|
+
# Get the iterations from the server response.
|
413
|
+
#
|
414
|
+
# @api private
|
415
|
+
#
|
416
|
+
# @since 1.12.0
|
417
|
+
def iterations
|
418
|
+
@iterations ||= payload_data.match(ITERATIONS)[1].to_i
|
419
|
+
end
|
420
|
+
|
421
|
+
# Get the data from the returned payload.
|
422
|
+
#
|
423
|
+
# @api private
|
424
|
+
#
|
425
|
+
# @since 1.12.0
|
426
|
+
def payload_data
|
427
|
+
reply[PAYLOAD].to_s
|
428
|
+
end
|
429
|
+
|
430
|
+
# Get the server nonce from the payload.
|
431
|
+
#
|
432
|
+
# @api private
|
433
|
+
#
|
434
|
+
# @since 1.12.0
|
435
|
+
def rnonce
|
436
|
+
@rnonce ||= payload_data.match(RNONCE)[1]
|
437
|
+
end
|
438
|
+
|
439
|
+
# Gets the salt from the server response.
|
440
|
+
#
|
441
|
+
# @api private
|
442
|
+
#
|
443
|
+
# @since 1.12.0
|
444
|
+
def salt
|
445
|
+
@salt ||= payload_data.match(SALT)[1]
|
446
|
+
end
|
447
|
+
|
448
|
+
# Salted password algorithm implementation.
|
449
|
+
#
|
450
|
+
# @api private
|
451
|
+
#
|
452
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
453
|
+
#
|
454
|
+
# @since 1.12.0
|
455
|
+
def salted_password
|
456
|
+
@salted_password ||= hi(hashed_password)
|
457
|
+
end
|
458
|
+
|
459
|
+
# Server key algorithm implementation.
|
460
|
+
#
|
461
|
+
# @api private
|
462
|
+
#
|
463
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
464
|
+
#
|
465
|
+
# @since 1.12.0
|
466
|
+
def server_key
|
467
|
+
@server_key ||= hmac(salted_password, SERVER_KEY)
|
468
|
+
end
|
469
|
+
|
470
|
+
if Base64.respond_to?(:strict_encode64)
|
471
|
+
|
472
|
+
# Server signature algorithm implementation.
|
473
|
+
#
|
474
|
+
# @api private
|
475
|
+
#
|
476
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
477
|
+
#
|
478
|
+
# @since 1.12.0
|
479
|
+
def server_signature
|
480
|
+
@server_signature ||= Base64.strict_encode64(hmac(server_key, auth_message))
|
481
|
+
end
|
482
|
+
else
|
483
|
+
|
484
|
+
# Server signature algorithm implementation.
|
485
|
+
#
|
486
|
+
# @api private
|
487
|
+
#
|
488
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
489
|
+
#
|
490
|
+
# @since 1.12.0
|
491
|
+
def server_signature
|
492
|
+
@server_signature ||= Base64.encode64(hmac(server_key, auth_message)).gsub("\n",'')
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
# Stored key algorithm implementation.
|
497
|
+
#
|
498
|
+
# @api private
|
499
|
+
#
|
500
|
+
# @see http://tools.ietf.org/html/rfc5802#section-3
|
501
|
+
#
|
502
|
+
# @since 1.12.0
|
503
|
+
def stored_key(key)
|
504
|
+
h(key)
|
505
|
+
end
|
506
|
+
|
507
|
+
# Get the verifier token from the server response.
|
508
|
+
#
|
509
|
+
# @api private
|
510
|
+
#
|
511
|
+
# @since 1.12.0
|
512
|
+
def verifier
|
513
|
+
@verifier ||= payload_data.match(VERIFIER)[1]
|
514
|
+
end
|
515
|
+
|
516
|
+
# Get the without proof message.
|
517
|
+
#
|
518
|
+
# @api private
|
519
|
+
#
|
520
|
+
# @see http://tools.ietf.org/html/rfc5802#section-7
|
521
|
+
#
|
522
|
+
# @since 1.12.0
|
523
|
+
def without_proof
|
524
|
+
@without_proof ||= "c=biws,r=#{rnonce}"
|
525
|
+
end
|
526
|
+
|
527
|
+
# XOR operation for two strings.
|
528
|
+
#
|
529
|
+
# @api private
|
530
|
+
#
|
531
|
+
# @since 1.12.0
|
532
|
+
def xor(first, second)
|
533
|
+
first.bytes.zip(second.bytes).map{ |(a,b)| (a ^ b).chr }.join('')
|
534
|
+
end
|
535
|
+
|
536
|
+
def validate_final_message!(reply)
|
537
|
+
validate!(reply)
|
538
|
+
unless verifier == server_signature
|
539
|
+
raise InvalidSignature.new(verifier, server_signature)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
def validate_first_message!(reply)
|
544
|
+
validate!(reply)
|
545
|
+
raise InvalidNonce.new(nonce, rnonce) unless rnonce.start_with?(nonce)
|
546
|
+
end
|
547
|
+
|
548
|
+
def validate!(reply)
|
549
|
+
unless Support.ok?(reply)
|
550
|
+
raise AuthenticationError, "Could not authorize user #{auth[:username]} on database #{auth[:db_name]}."
|
551
|
+
end
|
552
|
+
@reply = reply
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|