sspi.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. "use strict";
  2. var f = require('util').format
  3. , require_optional = require('require_optional')
  4. , Query = require('../connection/commands').Query
  5. , MongoError = require('../error');
  6. var AuthSession = function(db, username, password, options) {
  7. this.db = db;
  8. this.username = username;
  9. this.password = password;
  10. this.options = options;
  11. }
  12. AuthSession.prototype.equal = function(session) {
  13. return session.db == this.db
  14. && session.username == this.username
  15. && session.password == this.password;
  16. }
  17. // Kerberos class
  18. var Kerberos = null;
  19. var MongoAuthProcess = null;
  20. // Try to grab the Kerberos class
  21. try {
  22. Kerberos = require_optional('kerberos').Kerberos
  23. // Authentication process for Mongo
  24. MongoAuthProcess = require_optional('kerberos').processes.MongoAuthProcess
  25. } catch(err) {}
  26. /**
  27. * Creates a new SSPI authentication mechanism
  28. * @class
  29. * @return {SSPI} A cursor instance
  30. */
  31. var SSPI = function(bson) {
  32. this.bson = bson;
  33. this.authStore = [];
  34. }
  35. /**
  36. * Authenticate
  37. * @method
  38. * @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
  39. * @param {[]Connections} connections Connections to authenticate using this authenticator
  40. * @param {string} db Name of the database
  41. * @param {string} username Username
  42. * @param {string} password Password
  43. * @param {authResultCallback} callback The callback to return the result from the authentication
  44. * @return {object}
  45. */
  46. SSPI.prototype.auth = function(server, connections, db, username, password, options, callback) {
  47. var self = this;
  48. // We don't have the Kerberos library
  49. if(Kerberos == null) return callback(new Error("Kerberos library is not installed"));
  50. var gssapiServiceName = options['gssapiServiceName'] || 'mongodb';
  51. // Total connections
  52. var count = connections.length;
  53. if(count == 0) return callback(null, null);
  54. // Valid connections
  55. var numberOfValidConnections = 0;
  56. var errorObject = null;
  57. // For each connection we need to authenticate
  58. while(connections.length > 0) {
  59. // Execute MongoCR
  60. var execute = function(connection) {
  61. // Start Auth process for a connection
  62. SSIPAuthenticate(self, username, password, gssapiServiceName, server, connection, options, function(err, r) {
  63. // Adjust count
  64. count = count - 1;
  65. // If we have an error
  66. if(err) {
  67. errorObject = err;
  68. } else if(r && typeof r == 'object' && r.result['$err']) {
  69. errorObject = r.result;
  70. } else if(r && typeof r == 'object' && r.result['errmsg']) {
  71. errorObject = r.result;
  72. } else {
  73. numberOfValidConnections = numberOfValidConnections + 1;
  74. }
  75. // We have authenticated all connections
  76. if(count == 0 && numberOfValidConnections > 0) {
  77. // Store the auth details
  78. addAuthSession(self.authStore, new AuthSession(db, username, password, options));
  79. // Return correct authentication
  80. callback(null, true);
  81. } else if(count == 0) {
  82. if(errorObject == null) errorObject = new MongoError(f("failed to authenticate using mongocr"));
  83. callback(errorObject, false);
  84. }
  85. });
  86. }
  87. var _execute = function(_connection) {
  88. process.nextTick(function() {
  89. execute(_connection);
  90. });
  91. }
  92. _execute(connections.shift());
  93. }
  94. }
  95. var SSIPAuthenticate = function(self, username, password, gssapiServiceName, server, connection, options, callback) {
  96. // Build Authentication command to send to MongoDB
  97. var command = {
  98. saslStart: 1
  99. , mechanism: 'GSSAPI'
  100. , payload: ''
  101. , autoAuthorize: 1
  102. };
  103. // Create authenticator
  104. var mongo_auth_process = new MongoAuthProcess(connection.host, connection.port, gssapiServiceName, options);
  105. // Execute first sasl step
  106. server(connection, new Query(self.bson, "$external.$cmd", command, {
  107. numberToSkip: 0, numberToReturn: 1
  108. }), function(err, r) {
  109. if(err) return callback(err, false);
  110. var doc = r.result;
  111. mongo_auth_process.init(username, password, function(err) {
  112. if(err) return callback(err);
  113. mongo_auth_process.transition(doc.payload, function(err, payload) {
  114. if(err) return callback(err);
  115. // Perform the next step against mongod
  116. var command = {
  117. saslContinue: 1
  118. , conversationId: doc.conversationId
  119. , payload: payload
  120. };
  121. // Execute the command
  122. server(connection, new Query(self.bson, "$external.$cmd", command, {
  123. numberToSkip: 0, numberToReturn: 1
  124. }), function(err, r) {
  125. if(err) return callback(err, false);
  126. var doc = r.result;
  127. mongo_auth_process.transition(doc.payload, function(err, payload) {
  128. if(err) return callback(err);
  129. // Perform the next step against mongod
  130. var command = {
  131. saslContinue: 1
  132. , conversationId: doc.conversationId
  133. , payload: payload
  134. };
  135. // Execute the command
  136. server(connection, new Query(self.bson, "$external.$cmd", command, {
  137. numberToSkip: 0, numberToReturn: 1
  138. }), function(err, r) {
  139. if(err) return callback(err, false);
  140. var doc = r.result;
  141. mongo_auth_process.transition(doc.payload, function(err, payload) {
  142. // Perform the next step against mongod
  143. var command = {
  144. saslContinue: 1
  145. , conversationId: doc.conversationId
  146. , payload: payload
  147. };
  148. // Execute the command
  149. server(connection, new Query(self.bson, "$external.$cmd", command, {
  150. numberToSkip: 0, numberToReturn: 1
  151. }), function(err, r) {
  152. if(err) return callback(err, false);
  153. var doc = r.result;
  154. if(doc.done) return callback(null, true);
  155. callback(new Error("Authentication failed"), false);
  156. });
  157. });
  158. });
  159. });
  160. });
  161. });
  162. });
  163. });
  164. }
  165. // Add to store only if it does not exist
  166. var addAuthSession = function(authStore, session) {
  167. var found = false;
  168. for(var i = 0; i < authStore.length; i++) {
  169. if(authStore[i].equal(session)) {
  170. found = true;
  171. break;
  172. }
  173. }
  174. if(!found) authStore.push(session);
  175. }
  176. /**
  177. * Remove authStore credentials
  178. * @method
  179. * @param {string} db Name of database we are removing authStore details about
  180. * @return {object}
  181. */
  182. SSPI.prototype.logout = function(dbName) {
  183. this.authStore = this.authStore.filter(function(x) {
  184. return x.db != dbName;
  185. });
  186. }
  187. /**
  188. * Re authenticate pool
  189. * @method
  190. * @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
  191. * @param {[]Connections} connections Connections to authenticate using this authenticator
  192. * @param {authResultCallback} callback The callback to return the result from the authentication
  193. * @return {object}
  194. */
  195. SSPI.prototype.reauthenticate = function(server, connections, callback) {
  196. var authStore = this.authStore.slice(0);
  197. var count = authStore.length;
  198. if(count == 0) return callback(null, null);
  199. // Iterate over all the auth details stored
  200. for(var i = 0; i < authStore.length; i++) {
  201. this.auth(server, connections, authStore[i].db, authStore[i].username, authStore[i].password, authStore[i].options, function(err) {
  202. count = count - 1;
  203. // Done re-authenticating
  204. if(count == 0) {
  205. callback(err, null);
  206. }
  207. });
  208. }
  209. }
  210. /**
  211. * This is a result from a authentication strategy
  212. *
  213. * @callback authResultCallback
  214. * @param {error} error An error object. Set to null if no error present
  215. * @param {boolean} result The result of the authentication process
  216. */
  217. module.exports = SSPI;