em-mongo 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fc5694302e6f8f0c1a436480b2cf62bd5a6f12f6
4
- data.tar.gz: a0a7642623266b5080405ff926cac3dbaab8e91c
3
+ metadata.gz: 3f0b2c50ede300816de67e11528b73a7d1e337f1
4
+ data.tar.gz: fbc4c31accd66a72abf2aa1e70397cf4c66fcd6b
5
5
  SHA512:
6
- metadata.gz: ec15f1759525115e4a888b69c18514f0a30f043dfcf3f9e0b485ec13e21e6e88a28443762688c7111cfe8c7b06bc137a697b0138eff673a7d074386a7e4b45ed
7
- data.tar.gz: af2937db963c6a8e6b4997f611027737cf330af79ca7f7822c9efe362e99bac1a786114023463b3199a7b502c6b298fc079df664c477b6fb009803d8c2314ad8
6
+ metadata.gz: cca17b4be1e91a7d1a787054a49e3205a237eddebd44387134e4085c20337c87f1375e3a6da5bab1b98ab971f8dd3ac56bda6675a147ef0097a5d6150c4cc0e5
7
+ data.tar.gz: c0f8f8f69281f7046e5a9b91b5c819895224d4fa1edf0e6db620ab901c5ef1a7a0d1ef23f62c51be2706d5ecc7bd79c4433d1c1cc15f179e4b9e5725a96370bd
data/.gitignore CHANGED
@@ -4,3 +4,5 @@ Gemfile.lock
4
4
  vendor
5
5
  .rvmrc
6
6
  .ruby-version
7
+ .idea
8
+ *~
data/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ - 0.6.0
2
+
3
+ * add SCRAM-SHA-1 authentication mechanism
4
+ * add aggregation pipeline
5
+ * allow passing options to create_collection
6
+
7
+ * fix usage of em-mongo git repos with bundler
8
+ * update dependencies to bson <= 2.0, eventmachine >=0.12.10, <= 2.0
9
+
1
10
  - 0.5.1
2
11
 
3
12
  * fixed bson dependency (do not use 2.x)
@@ -1,6 +1,5 @@
1
1
 
2
- Em-mongo is no longer being maintained (hasn't been for some time). If you are interested in the commit bit
3
- or want to take over the full project, please let me know!
2
+ Em-mongo needs collaborators to help maintaining this project. If you are interested, please let me know!
4
3
 
5
4
  = EM-Mongo
6
5
 
@@ -122,6 +121,22 @@ In addition to calling your errback if the write fails, you can provide the usua
122
121
 
123
122
  safe_insert( {:a=>"v"}, :last_error_params => { :fsync => true, :w => 5 } )
124
123
 
124
+
125
+ == Authentication
126
+ At the moment the mechhanisms SCRAM-SHA-1(:scram_sha1) and MONGODB-CR(:mongodb_cr) are supported.
127
+ It works as follows:
128
+
129
+ # establish connection and get authentication db
130
+ connection = EM::Mongo::Connection.new(db_server, port)
131
+ auth_db = connection.db(db_name)
132
+
133
+ # authentication itself
134
+ resp = auth_db.authenticate(user,password, :scram_sha1)
135
+ resp.callback do |success|
136
+ do_authenticated #auth successful
137
+ end
138
+ resp.errback {|err| auth_failure err} # authentication failed
139
+
125
140
  == Documentation
126
141
 
127
142
  em-mongo now has some YARD docs. These are mostly ported directly from the mongo-ruby-driver. While they have been updated to reflect em-mongo's async API, there are probably a few errors left over in the translation. Please file an issue or submit a pull request if you notice any inaccuracies.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.1
1
+ 0.6.0
@@ -1,8 +1,10 @@
1
- version = File.read("VERSION").strip
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'em-mongo/version.rb' #for version
2
4
 
3
5
  Gem::Specification.new do |s|
4
6
  s.name = 'em-mongo'
5
- s.version = version
7
+ s.version = EventMachine::Mongo::VERSION
6
8
 
7
9
  s.authors = ['bcg', 'PlasticLizard']
8
10
  s.email = 'brenden.grace@gmail.com'
@@ -23,6 +25,6 @@ Gem::Specification.new do |s|
23
25
 
24
26
  s.summary = 'An EventMachine driver for MongoDB.'
25
27
 
26
- s.add_dependency 'eventmachine', ['>= 0.12.10']
27
- s.add_dependency "bson", ["~> 1.9.2"]
28
+ s.add_dependency 'eventmachine', ['>=0.12.10', '< 2.0']
29
+ s.add_dependency 'bson', ['>=1.9.2' , '< 2.0']
28
30
  end
@@ -5,9 +5,16 @@ require 'em-mongo/prev.rb'
5
5
  require 'eventmachine'
6
6
 
7
7
  EM.run do
8
- conn = EM::Mongo::Connection.new('localhost')
8
+ conn = EM::Mongo::Connection.new('localhost',27017)
9
9
  db = conn.db('my_database')
10
10
  collection = db.collection('my_collection')
11
+
12
+ resp = conn.db('admin').authenticate('test','test')
13
+
14
+ resp.callback{|response| puts "successfully authenticated #{response}"}
15
+ resp.errback {|response| puts "error on authentication #{response}"; return}
16
+
17
+
11
18
  EM.next_tick do
12
19
 
13
20
  (1..10).each do |i|
@@ -5,18 +5,14 @@ rescue LoadError
5
5
  require "bson"
6
6
  end
7
7
 
8
- module EM::Mongo
9
8
 
10
- module Version
11
- STRING = File.read(File.dirname(__FILE__) + '/../VERSION')
12
- MAJOR, MINOR, TINY = STRING.split('.')
13
- end
9
+ module EM::Mongo
14
10
 
15
11
  NAME = 'em-mongo'
16
12
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
17
13
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
18
14
  end
19
-
15
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/version")
20
16
  require File.join(EM::Mongo::LIBPATH, "em-mongo/conversions")
21
17
  require File.join(EM::Mongo::LIBPATH, "em-mongo/support")
22
18
  require File.join(EM::Mongo::LIBPATH, "em-mongo/database")
@@ -27,5 +23,7 @@ require File.join(EM::Mongo::LIBPATH, "em-mongo/cursor")
27
23
  require File.join(EM::Mongo::LIBPATH, "em-mongo/request_response")
28
24
  require File.join(EM::Mongo::LIBPATH, "em-mongo/server_response")
29
25
  require File.join(EM::Mongo::LIBPATH, "em-mongo/core_ext")
26
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/auth/Authentication.rb")
27
+
30
28
 
31
29
  EMMongo = EM::Mongo
@@ -0,0 +1,37 @@
1
+ require 'eventmachine'
2
+
3
+ # interface for all possible authentications
4
+ module EM::Mongo
5
+ class Authentication
6
+ include EM::Deferrable
7
+
8
+ SYSTEM_COMMAND_COLLECTION = '$cmd'
9
+
10
+ # supported AuthMethods (TODO make instantiation (in database.authenticate) dynamic)
11
+ module AuthMethod
12
+ SCRAM_SHA1 = :scram_sha1
13
+ MONGODB_CR = :mongodb_cr
14
+ end
15
+
16
+ def initialize(database)
17
+ @db = database
18
+ end
19
+
20
+ # Authenticate with the given username and password. Note that mongod
21
+ # must be started with the --auth option for authentication to be enabled.
22
+ #
23
+ # @param [String] username
24
+ # @param [String] password
25
+ #
26
+ # @return [EM::Mongo::RequestResponse] Calls back with +true+ or +false+, indicating success or failure
27
+ #
28
+ # @raise [AuthenticationError]
29
+ #
30
+ # @core authenticate authenticate-instance_method
31
+ def authenticate(username, password)
32
+ r=DefaultDeferrable.new #stub implementation
33
+ r.fail "not implemented, use a subclass instead"
34
+ return r
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,52 @@
1
+ require 'openssl'
2
+ require_relative '../support.rb'
3
+
4
+ module EM::Mongo
5
+ class MONGODB_CR < Authentication
6
+
7
+ MECHANISM = 'MONGODB-CR'.freeze
8
+
9
+ def authenticate(username, password)
10
+ response = RequestResponse.new
11
+
12
+ auth_resp = @db.collection(SYSTEM_COMMAND_COLLECTION).first({'getnonce' => 1})
13
+ auth_resp.callback do |res|
14
+ if not res or not res['nonce']
15
+ if res.nil? then response.fail "connection failure"
16
+ else response.fail "invalid first server response: " + res.to_s
17
+ end
18
+ else
19
+ auth = BSON::OrderedHash.new
20
+ auth['authenticate'] = 1
21
+ auth['user'] = username
22
+ auth['nonce'] = res['nonce']
23
+ auth['key'] = auth_key(username, password, res['nonce'])
24
+
25
+ auth_resp2 = @db.collection(SYSTEM_COMMAND_COLLECTION).first(auth)
26
+ auth_resp2.callback do |res|
27
+ if Support.ok?(res)
28
+ response.succeed true
29
+ else
30
+ response.fail res
31
+ end
32
+ end
33
+ auth_resp2.errback { |err| response.fail err }
34
+ end
35
+ end
36
+ auth_resp.errback { |err| response.fail err }
37
+ response
38
+ end
39
+
40
+ # Generate an MD5 for authentication.
41
+ #
42
+ # @param [String] username
43
+ # @param [String] password
44
+ # @param [String] nonce
45
+ #
46
+ # @return [String] a key for db authentication.
47
+ def auth_key(username, password, nonce)
48
+ OpenSSL::Digest::MD5.hexdigest("#{nonce}#{username}#{Support.hash_password(username, password)}")
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,237 @@
1
+ require 'openssl'
2
+ require 'bson'
3
+ require 'eventmachine'
4
+
5
+ require_relative '../support.rb'
6
+
7
+ module EM::Mongo
8
+
9
+ # an RFC 5802 compilant SCRAM(-SHA-1) implementation
10
+ # for MongoDB-Authentication
11
+ #
12
+ # so everything is encapsulated, but the main part (PAYLOAD of messages) is RFC5802 compilant
13
+ class SCRAM < Authentication
14
+
15
+ MECHANISM = 'SCRAM-SHA-1'.freeze
16
+
17
+ DIGEST = OpenSSL::Digest::SHA1.new.freeze
18
+
19
+ CLIENT_FIRST_MESSAGE = { saslStart: 1, autoAuthorize: 1 }.freeze
20
+ CLIENT_FINAL_MESSAGE = CLIENT_EMPTY_MESSAGE = { saslContinue: 1 }.freeze
21
+
22
+
23
+ CLIENT_KEY = 'Client Key'.freeze
24
+ SERVER_KEY = 'Server Key'.freeze
25
+
26
+ RNONCE = /r=([^,]*)/.freeze
27
+ SALT = /s=([^,]*)/.freeze
28
+ ITERATIONS = /i=(\d+)/.freeze
29
+ VERIFIER = /v=([^,]*)/.freeze
30
+ PAYLOAD = 'payload'.freeze
31
+
32
+ # @param [String] username
33
+ # @param [String] password
34
+ #
35
+ # @return [EM::Mongo::RequestResponse] Calls back with +true+ or +false+, indicating success or failure
36
+ #
37
+ # @raise [AuthenticationError]
38
+ #
39
+ # @core authenticate authenticate-instance_method
40
+ def authenticate(username, password)
41
+ response = RequestResponse.new
42
+
43
+ #TODO look for fail-fast-ness (strange word!?)
44
+ #TODO Flatten Hierarchies
45
+ @username = username
46
+ @plain_password = password
47
+
48
+ gs2_header = 'n,,'
49
+ client_first_bare = "n=#{@username},r=#{client_nonce}"
50
+
51
+ client_first = BSON::Binary.new(gs2_header+client_first_bare) # client_first msg
52
+ client_first_msg = CLIENT_FIRST_MESSAGE.merge({PAYLOAD=>client_first, mechanism:MECHANISM})
53
+
54
+ client_first_resp = @db.collection(EM::Mongo::Database::SYSTEM_COMMAND_COLLECTION).first(client_first_msg) #TODO extract and make easier to understand (e.g. command(first_msg) or sthg like that)
55
+
56
+ #server_first_resp #for flattening
57
+
58
+ client_first_resp.callback do |res_first|
59
+ if not is_server_response_valid? res_first
60
+ response.fail "first server response not valid: " + res_first.to_s
61
+ else
62
+ # take the salt & iterations and do the pw-derivation
63
+ server_first = res_first[PAYLOAD].to_s
64
+
65
+ @conversation_id=conv_id = res_first['conversationId']
66
+
67
+ combined_nonce = server_first.match(RNONCE)[1] #r= ...
68
+ salt = server_first.match( SALT )[1] #s=... (from server_first)
69
+ iterations = server_first.match(ITERATIONS)[1].to_i #i=... ..
70
+
71
+ if not combined_nonce.start_with?(client_nonce) # combined_nonce should be client_nonce+server_nonce
72
+ response.fail "nonce doesn't start with client_nonce: " + res_first.to_s
73
+ else
74
+ client_final_wo_proof= "c=#{Base64.strict_encode64(gs2_header)},r=#{combined_nonce}" #c='biws'
75
+ auth_message = client_first_bare + ',' + server_first + ',' + client_final_wo_proof
76
+
77
+ # proof = clientKey XOR clientSig ## needs to be sent back
78
+ #
79
+ # ClientSign = HMAC(StoredKey, AuthMessage)
80
+ # StoredKey = H(ClientKey) ## lt. RFC5802 (needs to be verified against ruby-mongo driver impl)
81
+ # AuthMessage = client_first_bare + ','+server_first+','+client_final_wo_proof
82
+
83
+ @salt = salt
84
+ @iterations = iterations
85
+ #client_key = client_key()
86
+
87
+ @auth_message = auth_message
88
+ #client_signature = client_signature()
89
+
90
+ proof = Base64.strict_encode64(xor(client_key, client_signature))
91
+ client_final = BSON::Binary.new ( client_final_wo_proof + ",p=#{proof}")
92
+ client_final_msg = CLIENT_FINAL_MESSAGE.merge({PAYLOAD => client_final, conversationId: conv_id})
93
+
94
+ client_final_resp = @db.collection(SYSTEM_COMMAND_COLLECTION).first(client_final_msg)
95
+ client_final_resp.callback do |res_final|
96
+ if not is_server_response_valid? res_final
97
+ response.fail "Final Server Response not valid " + res_final.to_s
98
+ else
99
+ server_final = res_final[PAYLOAD].to_s # in RFC this equals server_final
100
+ verifier = server_final.match(VERIFIER)[1] #r= ...
101
+ if verifier and verifier_valid? verifier
102
+ handle_server_end(response,conv_id) # will set the response
103
+ else
104
+ response.fail "verifier #{verifier.nil? ? 'not present':'invalid'} #{res_final}"
105
+ end
106
+ end
107
+ end
108
+ client_final_resp.errback { |err| response.fail err }
109
+ end
110
+ end
111
+ end
112
+ client_first_resp.errback {
113
+ |err| response.fail err }
114
+ return response
115
+ end
116
+
117
+
118
+ # MongoDB handles the end of authentication different than in RFC 5802
119
+ # it needs at least an additional empty response (this needs to be iterated until res[done]=true
120
+ # (at least it is done so in the official mongo-ruby-drive (at least it is done so in the official mongo-ruby-driver))
121
+ # -> recursion (is technically more loop than recursion but here it's one)
122
+ #
123
+ # @param response [EM::Mongo::ResponseRequest] to fail or succeed after completion
124
+ # @param conv_id ConversationId to send to the server on each iteration
125
+ def handle_server_end(response,conv_id) # will set the response
126
+ client_end = BSON::Binary.new('')
127
+ client_end_msg = CLIENT_EMPTY_MESSAGE.merge(PAYLOAD=>client_end, conversationId:conv_id)
128
+ server_end_resp = @db.collection(SYSTEM_COMMAND_COLLECTION).first(client_end_msg)
129
+
130
+ server_end_resp.errback{|err| response.fail err}
131
+
132
+ server_end_resp.callback do |res|
133
+ if not is_server_response_valid? res
134
+ response.fail "got invalid response on handling server_end: #{res.nil? ? 'nil' : res}"
135
+ else
136
+ if res['done'] == true || res['done'] == 'true'
137
+ response.succeed true
138
+ else
139
+ handle_server_end(response,conv_id) # try it again
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ # to be valid the response has to
146
+ # * be not nil
147
+ # * contain at least ['done'], ['ok'], ['payload'], ['conversationId']
148
+ # * ['ok'].to_i has to be 1
149
+ # * ['conversationId'] has to match the first sent one
150
+ # @param [BSON::OrderedHash] response the response got from server
151
+ def is_server_response_valid?(response)
152
+ if response.nil? then return false; end
153
+ if response['done'].nil? or
154
+ response['ok'].nil? or
155
+ response['payload'].nil? or
156
+ response['conversationId'].nil? then
157
+ return false;
158
+ end
159
+
160
+ if not Support.ok? response then return false; end
161
+ if not @conversation_id.nil? and response['conversationId'] != @conversation_id
162
+ return false;
163
+ end
164
+
165
+ true
166
+ end
167
+
168
+ ## verify the verifier (v=...)
169
+ def verifier_valid?(verifier)
170
+ verifier == server_signature
171
+ end
172
+
173
+
174
+ ### Building blocks
175
+ # @see http://tools.ietf.org/html/rfc5802#section-2.2
176
+
177
+ def hi(password, salt, iterations)
178
+ OpenSSL::PKCS5.pbkdf2_hmac_sha1(
179
+ password,
180
+ Base64.strict_decode64(salt),
181
+ iterations,
182
+ DIGEST.size
183
+ )
184
+ end
185
+
186
+ def hmac(data,key)
187
+ OpenSSL::HMAC.digest(DIGEST, data, key)
188
+ end
189
+
190
+ # xor for strings
191
+ def xor(first, second)
192
+ first.bytes
193
+ .zip(second.bytes)
194
+ .map{|(x,y)| (x ^ y).chr}
195
+ .join('')
196
+ end
197
+
198
+
199
+ def client_nonce
200
+ @client_nonce ||= SecureRandom.base64
201
+ end
202
+
203
+ # needs @username, @plain_password defined
204
+ def hashed_password
205
+ @hashed_password ||= Support.hash_password(@username, @plain_password).encode("UTF-8")
206
+ end
207
+
208
+ #needs @username, @plain_password, @salt, @iterations defined
209
+ def salted_password
210
+ @salted_password ||= hi(hashed_password, @salt, @iterations)
211
+ end
212
+
213
+ # @see http://tools.ietf.org/html/rfc5802#section-3
214
+ def client_key
215
+ @client_key ||= hmac(salted_password,CLIENT_KEY)
216
+ end
217
+ # server_key = hmac(salted_password,"Server Key")
218
+ def server_key
219
+ @server_key ||= hmac(salted_password,SERVER_KEY)
220
+ end
221
+
222
+ #needs @username, @plain_password, @salt, @iterations, @auth_message defined
223
+ def client_signature
224
+ @client_signature ||= hmac(DIGEST.digest(client_key), @auth_message)
225
+ end
226
+
227
+ # server_signature = B64(hmac(server_key, auth_message)
228
+ def server_signature
229
+ @server_signature ||= Base64.strict_encode64(hmac(server_key, @auth_message))
230
+ end
231
+
232
+ class FirstMessage
233
+ include EM::Deferrable
234
+
235
+ end
236
+ end
237
+ end
@@ -401,6 +401,59 @@ module EM::Mongo
401
401
  response
402
402
  end
403
403
 
404
+ # Perform an aggregation using the aggregation framework on the current collection.
405
+ # @note Aggregate requires server version >= 2.1.1
406
+ # @note Field References: Within an expression, field names must be quoted and prefixed by a dollar sign ($).
407
+ #
408
+ # @example Define the pipeline as an array of operator hashes:
409
+ # coll.aggregate([ {"$project" => {"last_name" => 1, "first_name" => 1 }}, {"$match" => {"last_name" => "Jones"}} ])
410
+ #
411
+ # @param [Array] pipeline Should be a single array of pipeline operator hashes.
412
+ #
413
+ # '$project' Reshapes a document stream by including fields, excluding fields, inserting computed fields,
414
+ # renaming fields,or creating/populating fields that hold sub-documents.
415
+ #
416
+ # '$match' Query-like interface for filtering documents out of the aggregation pipeline.
417
+ #
418
+ # '$limit' Restricts the number of documents that pass through the pipeline.
419
+ #
420
+ # '$skip' Skips over the specified number of documents and passes the rest along the pipeline.
421
+ #
422
+ # '$unwind' Peels off elements of an array individually, returning one document for each member.
423
+ #
424
+ # '$group' Groups documents for calculating aggregate values.
425
+ #
426
+ # '$sort' Sorts all input documents and returns them to the pipeline in sorted order.
427
+ #
428
+ # @option opts [:primary, :secondary] :read Read preference indicating which server to perform this query
429
+ # on. See Collection#find for more details.
430
+ # @option opts [String] :comment (nil) a comment to include in profiling logs
431
+ #
432
+ # @return [Array] An Array with the aggregate command's results.
433
+ #
434
+ # @raise MongoArgumentError if operators either aren't supplied or aren't in the correct format.
435
+ # @raise MongoOperationFailure if the aggregate command fails.
436
+ #
437
+ def aggregate(pipeline=nil, opts={})
438
+ response = RequestResponse.new
439
+ raise MongoArgumentError, "pipeline must be an array of operators" unless pipeline.class == Array
440
+ raise MongoArgumentError, "pipeline operators must be hashes" unless pipeline.all? { |op| op.class == Hash }
441
+
442
+ hash = BSON::OrderedHash.new
443
+ hash['aggregate'] = self.name
444
+ hash['pipeline'] = pipeline
445
+
446
+ cmd_resp = db.command(hash)
447
+ cmd_resp.callback do |resp|
448
+ response.succeed resp["result"]
449
+ end
450
+ cmd_resp.errback do |err|
451
+ response.fail err
452
+ end
453
+
454
+ response
455
+ end
456
+
404
457
  # Perform a map-reduce operation on the current collection.
405
458
  #
406
459
  # @param [String, BSON::Code] map a map function, written in JavaScript.
@@ -809,3 +862,4 @@ module EM::Mongo
809
862
 
810
863
  end
811
864
  end
865
+
@@ -27,7 +27,6 @@ module EM::Mongo
27
27
  SPHERE2D = '2dsphere'
28
28
  GEO2D = '2d'
29
29
 
30
- DEFAULT_MAX_BSON_SIZE = 4 * 1024 * 1024
31
30
 
32
31
  class EMConnection < EM::Connection
33
32
  MAX_RETRIES = 5
@@ -1,3 +1,7 @@
1
+ require_relative 'auth/Authentication.rb'
2
+ require_relative 'auth/scram.rb'
3
+ require_relative 'auth/mongodb_cr.rb'
4
+
1
5
  module EM::Mongo
2
6
  class Database
3
7
 
@@ -83,7 +87,7 @@ module EM::Mongo
83
87
  # a cursor which can be iterated over. For each collection, a hash
84
88
  # will be yielded containing a 'name' string and, optionally, an 'options' hash.
85
89
  #
86
- # @param [String] coll_name return info for the specifed collection only.
90
+ # @param [String] coll_name return info for the specified collection only.
87
91
  #
88
92
  # @return [EM::Mongo::Cursor]
89
93
  def collections_info(coll_name=nil)
@@ -114,7 +118,7 @@ module EM::Mongo
114
118
  # already exists or collection creation fails on the server.
115
119
  #
116
120
  # @return [EM::Mongo::RequestResponse] Calls back with the new collection
117
- def create_collection(name)
121
+ def create_collection(name, opts = {})
118
122
  response = RequestResponse.new
119
123
  names_resp = collection_names
120
124
  names_resp.callback do |names|
@@ -125,6 +129,7 @@ module EM::Mongo
125
129
  # Create a new collection.
126
130
  oh = BSON::OrderedHash.new
127
131
  oh[:create] = name
132
+ oh.merge! opts
128
133
  cmd_resp = command(oh)
129
134
  cmd_resp.callback do |doc|
130
135
  if EM::Mongo::Support.ok?(doc)
@@ -302,7 +307,7 @@ module EM::Mongo
302
307
  check_response = opts.fetch(:check_response, true)
303
308
  raise MongoArgumentError, "command must be given a selector" unless selector.is_a?(Hash) && !selector.empty?
304
309
 
305
- if selector.keys.length > 1 && RUBY_VERSION < '1.9' && selector.class != BSON::OrderedHash
310
+ if selector.size > 1 && RUBY_VERSION < '1.9' && selector.class != BSON::OrderedHash
306
311
  raise MongoArgumentError, "DB#command requires an OrderedHash when hash contains multiple keys"
307
312
  end
308
313
 
@@ -331,38 +336,20 @@ module EM::Mongo
331
336
  #
332
337
  # @param [String] username
333
338
  # @param [String] password
339
+ # @param [Authentication::AuthMethod] auth_method, defaults to MONGODB_CR for downward compatibility
334
340
  #
335
341
  # @return [EM::Mongo::RequestResponse] Calls back with +true+ or +false+, indicating success or failure
336
342
  #
337
343
  # @raise [AuthenticationError]
338
344
  #
339
345
  # @core authenticate authenticate-instance_method
340
- def authenticate(username, password)
341
- response = RequestResponse.new
342
- auth_resp = self.collection(SYSTEM_COMMAND_COLLECTION).first({'getnonce' => 1})
343
- auth_resp.callback do |res|
344
- if not res or not res['nonce']
345
- response.succeed false
346
- else
347
- auth = BSON::OrderedHash.new
348
- auth['authenticate'] = 1
349
- auth['user'] = username
350
- auth['nonce'] = res['nonce']
351
- auth['key'] = EM::Mongo::Support.auth_key(username, password, res['nonce'])
352
-
353
- auth_resp2 = self.collection(SYSTEM_COMMAND_COLLECTION).first(auth)
354
- auth_resp2.callback do |res|
355
- if EM::Mongo::Support.ok?(res)
356
- response.succeed true
357
- else
358
- response.fail res
359
- end
360
- end
361
- auth_resp2.errback { |err| response.fail err }
362
- end
363
- end
364
- auth_resp.errback { |err| response.fail err }
365
- response
346
+ def authenticate(username, password, auth_method=Authentication::AuthMethod::MONGODB_CR)
347
+ auth = case auth_method
348
+ when Authentication::AuthMethod::SCRAM_SHA1 then SCRAM.new self
349
+ when Authentication::AuthMethod::MONGODB_CR then MONGODB_CR.new self
350
+ else raise AuthenticationError.new("Authentication method #{auth_method} not supported")
351
+ end
352
+ return auth.authenticate(username, password)
366
353
  end
367
354
 
368
355
  # Adds a user to this database for use with authentication. If the user already
@@ -372,6 +359,7 @@ module EM::Mongo
372
359
  # @param [String] password
373
360
  #
374
361
  # @return [EM::Mongo::RequestResponse] Calls back with an object representing the user.
362
+ # #TODO check if that works as it should with SCRAM-SHA1
375
363
  def add_user(username, password)
376
364
  response = RequestResponse.new
377
365
  user_resp = self.collection(SYSTEM_USER_COLLECTION).first({:user => username})
@@ -16,23 +16,13 @@
16
16
  # limitations under the License.
17
17
  # ++
18
18
 
19
- require 'digest/md5'
19
+ require 'openssl'
20
20
 
21
21
  module EM::Mongo
22
22
  module Support
23
23
  include EM::Mongo::Conversions
24
24
  extend self
25
25
 
26
- # Generate an MD5 for authentication.
27
- #
28
- # @param [String] username
29
- # @param [String] password
30
- # @param [String] nonce
31
- #
32
- # @return [String] a key for db authentication.
33
- def auth_key(username, password, nonce)
34
- Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
35
- end
36
26
 
37
27
  # Return a hashed password for auth.
38
28
  #
@@ -41,7 +31,7 @@ module EM::Mongo
41
31
  #
42
32
  # @return [String]
43
33
  def hash_password(username, plaintext)
44
- Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
34
+ OpenSSL::Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
45
35
  end
46
36
 
47
37
 
@@ -0,0 +1,11 @@
1
+ module EventMachine
2
+ module Mongo
3
+
4
+ VERSION = File.read(File.expand_path('../../../VERSION',__FILE__)).strip
5
+
6
+ module Version
7
+ STRING = EventMachine::Mongo::VERSION
8
+ MAJOR, MINOR, TINY = STRING.split('.')
9
+ end
10
+ end
11
+ end
@@ -395,6 +395,19 @@ describe EMMongo::Collection do
395
395
 
396
396
  end
397
397
 
398
+ describe "aggregate" do
399
+ it "should aggregate" do
400
+ @coll << { :a => 1, :b => 1 }
401
+ @coll << { :a => 2, :b => 1 }
402
+ @coll << { :a => 3, :b => 1 }
403
+
404
+ resp = @coll.aggregate([{'$project' => {:a => 1}}, {'$group' => {:_id => :counts, :counts => {'$sum' => 1} }}])
405
+ resp.callback do |doc|
406
+ doc[0]["counts"].should == 3
407
+ end
408
+ end
409
+ end
410
+
398
411
  describe "mapreduce" do
399
412
  it "should map, and then reduce" do
400
413
  @conn, @coll = connection_and_collection
@@ -34,6 +34,18 @@ describe EMMongo::Database do
34
34
  end
35
35
  end
36
36
 
37
+ it "should create a collection with options" do
38
+ @conn = EM::Mongo::Connection.new
39
+ @db = @conn.db
40
+ @db.create_collection('capped', {:capped => true, :max => 10}).callback do |col|
41
+ @db.command({:collstats => 'capped'}).callback do |doc|
42
+ doc['capped'].should == 1
43
+ doc['max'].should == 10
44
+ done
45
+ end
46
+ end
47
+ end
48
+
37
49
  it "should drop a collection" do
38
50
  @conn = EM::Mongo::Connection.new
39
51
  @db = @conn.db
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-mongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - bcg
@@ -18,6 +18,9 @@ dependencies:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
20
  version: 0.12.10
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '2.0'
21
24
  type: :runtime
22
25
  prerelease: false
23
26
  version_requirements: !ruby/object:Gem::Requirement
@@ -25,20 +28,29 @@ dependencies:
25
28
  - - ">="
26
29
  - !ruby/object:Gem::Version
27
30
  version: 0.12.10
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
28
34
  - !ruby/object:Gem::Dependency
29
35
  name: bson
30
36
  requirement: !ruby/object:Gem::Requirement
31
37
  requirements:
32
- - - "~>"
38
+ - - ">="
33
39
  - !ruby/object:Gem::Version
34
40
  version: 1.9.2
41
+ - - "<"
42
+ - !ruby/object:Gem::Version
43
+ version: '2.0'
35
44
  type: :runtime
36
45
  prerelease: false
37
46
  version_requirements: !ruby/object:Gem::Requirement
38
47
  requirements:
39
- - - "~>"
48
+ - - ">="
40
49
  - !ruby/object:Gem::Version
41
50
  version: 1.9.2
51
+ - - "<"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
42
54
  description: EventMachine driver for MongoDB.
43
55
  email: brenden.grace@gmail.com
44
56
  executables: []
@@ -56,6 +68,9 @@ files:
56
68
  - examples/legacy.rb
57
69
  - examples/readme.rb
58
70
  - lib/em-mongo.rb
71
+ - lib/em-mongo/auth/Authentication.rb
72
+ - lib/em-mongo/auth/mongodb_cr.rb
73
+ - lib/em-mongo/auth/scram.rb
59
74
  - lib/em-mongo/collection.rb
60
75
  - lib/em-mongo/connection.rb
61
76
  - lib/em-mongo/conversions.rb
@@ -67,6 +82,7 @@ files:
67
82
  - lib/em-mongo/request_response.rb
68
83
  - lib/em-mongo/server_response.rb
69
84
  - lib/em-mongo/support.rb
85
+ - lib/em-mongo/version.rb
70
86
  - spec/gem/Gemfile
71
87
  - spec/gem/bundler.rb
72
88
  - spec/gem/rubygems.rb
@@ -97,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
113
  version: '0'
98
114
  requirements: []
99
115
  rubyforge_project: em-mongo
100
- rubygems_version: 2.2.0
116
+ rubygems_version: 2.5.2.3
101
117
  signing_key:
102
118
  specification_version: 4
103
119
  summary: An EventMachine driver for MongoDB.