sspi.js 7.1 KB

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