Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

GH-4 GH-9: added authorization command queuing + reauthorization on r…

…econnect

+ special thanks to https://github.com/IlskenLabs for helping with this
+ this hasn't been tested, but all the components should be there
  • Loading branch information...
commit 767d7b8a70e01b966256e8ee0f6c7742061833e5 1 parent 7680ddf
@marcello3d authored
View
6 lib/collection.js
@@ -66,7 +66,7 @@ MongolianCollection.prototype.insert = function(object, callback) {
})
var insertCommand = new mongo.InsertCommand(this.server._fakeDb, this.fullName)
insertCommand.documents = objects
- this.server.sendCommand(insertCommand)
+ this.db.sendCommand(insertCommand)
if (callback) {
this.db.lastError(safetyNet(callback, function() {
callback(null, object)
@@ -91,7 +91,7 @@ MongolianCollection.prototype.update = function(criteria, objNew, upsert, multi,
}
if (callback && !(callback instanceof Function)) throw new Error("callback is not a function!")
var updateCommand = new mongo.UpdateCommand(this.server._fakeDb, this.fullName, criteria, objNew, { upsert:upsert, multi:multi })
- this.server.sendCommand(updateCommand)
+ this.db.sendCommand(updateCommand)
if (callback) {
this.db.lastError(safetyNet(callback, function() {
callback(null)
@@ -135,7 +135,7 @@ MongolianCollection.prototype.remove = function(criteria, callback) {
}
if (callback && !(callback instanceof Function)) throw new Error("callback is not a function!")
var deleteCommand = new mongo.DeleteCommand(this.server._fakeDb, this.fullName, criteria)
- this.server.sendCommand(deleteCommand)
+ this.db.sendCommand(deleteCommand)
if (callback) {
this.db.lastError(safetyNet(callback, function() {
callback(null)
View
6 lib/cursor.js
@@ -25,7 +25,7 @@ MongolianCursor.prototype.reset = function() {
*/
MongolianCursor.prototype.close = function(callback) {
if (this._currentBatch && this._currentBatch.cursorId) {
- this.server.sendCommand(new mongo.KillCursorCommand(this.server._fakeDb, [this._currentBatch.cursorId]), callback)
+ this.db.sendCommand(new mongo.KillCursorCommand(this.server._fakeDb, [this._currentBatch.cursorId]), callback)
delete this._currentBatch.cursorId
}
}
@@ -132,7 +132,7 @@ MongolianCursor.prototype.nextBatch = function(callback) {
self._specials || self.criteria || {},
self.fields
)
- self.server.sendCommand(queryCommand, filterBatch)
+ self.db.sendCommand(queryCommand, filterBatch)
} else if (self._currentBatch.cursorId) {
var getMoreCommand = new mongo.GetMoreCommand(
self.server._fakeDb,
@@ -140,7 +140,7 @@ MongolianCursor.prototype.nextBatch = function(callback) {
retrieveCount,
self._currentBatch.cursorId
)
- self.server.sendCommand(getMoreCommand, filterBatch)
+ self.db.sendCommand(getMoreCommand, filterBatch)
} else {
callback(null, null)
}
View
70 lib/db.js
@@ -55,46 +55,90 @@ MongolianDB.prototype.collectionNames = function(callback) {
*/
MongolianDB.prototype.queryCommand = function(query, callback) {
this.collection('$cmd').findOne(query, safetyNet(callback, function(result) {
- if (!result.ok) throw new MongolianError("Mongo Result error\n" + require('util').inspect(result))
- callback(null, result)
+ if (!result.ok) {
+ var error = new Error("Mongo Result error: " + result.errmsg)
+ error.result = result
+ callback(error)
+ } else {
+ callback(null, result)
+ }
}))
}
+/**
+ * Sends a command to the server with authentication queueing
+ */
+MongolianDB.prototype.sendCommand = function(command, callback) {
+ if (!this._authorize || this.authorized || (command.query && (command.query.getnonce || command.query.authenticate))) {
+ this.server.sendCommand(command,callback)
+ } else {
+ this._authorizeQueue.push([command,callback])
+ }
+}
+function md5hash(string) {
+ return crypto.createHash('md5').update(string).digest('hex')
+}
+
+/**
+ * Adds user authentication to this database
+ */
MongolianDB.prototype.addUser = function(username, password, readOnly, callback) {
- readOnly = readOnly || false;
+ if (!callback && readOnly instanceof Function) {
+ callback = readOnly
+ readOnly = false
+ }
+ readOnly = readOnly || false
var users = this.collection("system.users")
users.findOne({ user:username }, safetyNet(callback, function(user) {
user = user || { user:username }
user.readOnly = readOnly
- user.pwd = crypto.createHash('md5').update(username + ":mongo:" + password).digest('hex')
- users.save(user)
+ user.pwd = md5hash(username + ":mongo:" + password)
+ console.log("saving user: ",user)
+ users.save(user, callback)
}))
}
+/**
+ * Removes a user (you'll need to be authenticated first)
+ */
MongolianDB.prototype.removeUser = function(username, callback) {
this.collection("system.users").remove({ user:username }, callback)
}
-
/**
* Authenticate the database
*/
-MongolianDB.prototype.auth = function(username, password) {
- this._auth = function(callback) {
- var self = this
+MongolianDB.prototype.auth = function(username, password, callback) {
+ var self = this
+ self._authorize = function(callback) {
+ if (!self._authorizeQueue) self._authorizeQueue = []
self.queryCommand({ getnonce:1 }, safetyNet(callback, function(nonce) {
- var hash_password = crypto.createHash('md5').update(username + ":mongo:" + password).digest('hex')
- var key = crypto.createHash('md5').update(nonce + username + hash_password).digest('hex')
self.queryCommand({
authenticate:1,
user:username,
nonce:nonce,
- key:key
- }, callback)
+ key:md5hash(nonce + username + md5hash(username + ":mongo:" + password))
+ }, function (error) {
+ var queue = self._authorizeQueue
+ delete self._authorizeQueue
+ if (!error) {
+ self._authorized = true
+ self.server.log.info("Authenticated "+self+" with user "+username)
+ }
+ if (callback) callback(error)
+ queue && queue.forEach(function(queued) {
+ if (error) {
+ queued[1](error)
+ } else {
+ self.server.sendCommand(queued[0], queued[1])
+ }
+ })
+ })
}))
}
+ self._authorize(callback)
}
/**
View
66 lib/server.js
@@ -72,16 +72,21 @@ MongolianServer.prototype.toString = function() {
* Closes the current connection, passing the optional error object to any pending request callbacks.
*/
MongolianServer.prototype.close = function(error) {
- var self = this
- if (self._connection) {
- self._connection.close()
- delete self._connection
- error = error || new Error("Connection closed")
- for (var requestId in self._callbacks) {
- self._callbacks[requestId](error)
+ error = error || new Error("Connection closed")
+ if (this._connection) {
+ this._connection.close()
+ delete this._connection
+ for (var requestId in this._callbacks) {
+ this._callbacks[requestId](error)
}
- self._callbacks = {}
- self._callbackCount = 0
+ this._callbacks = {}
+ this._callbackCount = 0
+ }
+ if (this._connectionCallbacks) {
+ this._connectionCallbacks.forEach(function(callback) {
+ callback(error)
+ })
+ delete this._connectionCallbacks
}
}
MongolianServer.prototype.log = {
@@ -106,18 +111,19 @@ MongolianServer.prototype._fakeDb = {
* Get raw connection to server
*/
MongolianServer.prototype._getConnection = function(callback) {
- var self = this;
- if (self._connection && !self._connection.connection.writable) {
- console.warn("Connection is not writable! --- Unsatisfied requests: "+Object.keys(self._callbacks).length)
- delete self._connection // don't close
+ if (this._connection && !this._connection.connection.writable) {
+ this.log.warn("Connection is not writable! --- Unsatisfied requests: "+Object.keys(this._callbacks).length)
+ delete this._connection // don't close
}
- if (self._connection) {
- callback(null, self._connection)
- } else if (self._connectionCallbacks) {
- self._connectionCallbacks.push(callback)
+ if (this._connection) {
+ callback(null, this._connection)
+ } else if (this._connectionCallbacks) {
+ this._connectionCallbacks.push(callback)
} else {
- self._connectionCallbacks = [callback]
- var connection = new mongo.Connection(self.host, self.port)
+ this._connectionCallbacks = [callback]
+ var connection = new mongo.Connection(this.host, this.port)
+
+ var self = this
connection.on('error', function(error) {
self.log.error("Connection error on "+self, error.stack)
self.close(error)
@@ -126,15 +132,21 @@ MongolianServer.prototype._getConnection = function(callback) {
self.log.info("Connection closed on "+self)
self.close()
})
- connection.on('connect',function(err) {
- if (err) {
- self.log.error("Error connecting to " + self, error.stack)
- } else {
- self.log.info("Connected to " + self)
- self._connection = connection
- self._connectionCallbacks.forEach(function(callback) { callback(null, connection) })
- delete self._connectionCallbacks
+ connection.on('connect',function() {
+ self.log.info("Connected to " + self)
+ self._connection = connection
+ // Re-authorize previously authorized databases
+ for (var name in self._dbs) {
+ var db = self._dbs[name]
+ if (db._authorized) {
+ db._authorized = false
+ db._authorize()
+ }
}
+ self._connectionCallbacks.forEach(function(callback) {
+ callback(null, connection)
+ })
+ delete self._connectionCallbacks
})
connection.on("data", function(message) {
var reply = new mongo.MongoReply(self._fakeDb, message)
View
23 lib/util.js
@@ -1,18 +1,6 @@
/* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
-
var util = require('util')
-/**
- * Used to represent asynchronous MongoDB errors
- * Note: this is NOT used for synchronous/"programmer" errors
- */
-function MongolianError() {
- Error.apply(this, [].slice.call(arguments))
-}
-MongolianError.prototype = new Error
-
-exports.MongolianError = MongolianError
-
//////////////////////////////////////////////////////////////////////////////////
// Internal
@@ -24,20 +12,11 @@ exports.MongolianError = MongolianError
* @returns an async function(err,value)
*/
exports.safetyNet = function(callback,body) {
- if (typeof body !== 'function') throw new Error("body is not a function")
return function(error) {
if (error) {
callback && callback(error)
} else {
- try {
- body.apply(this, Array.prototype.slice.call(arguments,1))
- } catch (ex) {
- if (ex instanceof MongolianError) {
- callback && callback(ex)
- } else {
- throw ex
- }
- }
+ body.apply(this, Array.prototype.slice.call(arguments,1))
}
}
}
View
28 test/simple-test.js
@@ -411,4 +411,32 @@ vows.describe('Mongolian DeadBeef, I choose you!').addBatch({
}
}
}
+//,
+// "with a clean DB 'testauth'": {
+// topic: function() {
+// var db = new Mongolian({ keepAlive:1 }).db('mongolian_test_auth')
+// var callback = this.callback
+// db.drop(function(err) { callback(err, !err && db) })
+// },
+// "is not null": function (db) {
+// assert.isObject(db)
+// },
+// "and try to find something": {
+// topic: function(db) {
+// db.collection("hello").find().toArray(this.callback)
+// },
+// "succeeds": function(result) {
+// assert.isArray(result)
+// }
+// },
+// "add user": {
+// topic: function(db) {
+// db.addUser("allo", "there", this.callback)
+// },
+// "succeeded":function(result) {
+// assert.isObject(result)
+// }
+//
+// }
+// }
}).export(module)
Please sign in to comment.
Something went wrong with that request. Please try again.