em-mongo 0.5.1 → 0.6.0

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 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.