gssapi.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. /**
  28. * Creates a new GSSAPI authentication mechanism
  29. * @class
  30. * @return {GSSAPI} A cursor instance
  31. */
  32. var GSSAPI = function(bson) {
  33. this.bson = bson;
  34. this.authStore = [];
  35. }
  36. /**
  37. * Authenticate
  38. * @method
  39. * @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
  40. * @param {[]Connections} connections Connections to authenticate using this authenticator
  41. * @param {string} db Name of the database
  42. * @param {string} username Username
  43. * @param {string} password Password
  44. * @param {authResultCallback} callback The callback to return the result from the authentication
  45. * @return {object}
  46. */
  47. GSSAPI.prototype.auth = function(server, connections, db, username, password, options, callback) {
  48. var self = this;
  49. // We don't have the Kerberos library
  50. if(Kerberos == null) return callback(new Error("Kerberos library is not installed"));
  51. var gssapiServiceName = options['gssapiServiceName'] || 'mongodb';
  52. // Total connections
  53. var count = connections.length;
  54. if(count == 0) return callback(null, null);
  55. // Valid connections
  56. var numberOfValidConnections = 0;
  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. GSSAPIInitialize(self, db, username, password, db, gssapiServiceName, server, connection, options, 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.result['$err']) {
  70. errorObject = r.result;
  71. } else if(r.result['errmsg']) {
  72. errorObject = r.result;
  73. } else {
  74. numberOfValidConnections = numberOfValidConnections + 1;
  75. }
  76. // We have authenticated all connections
  77. if(count == 0 && numberOfValidConnections > 0) {
  78. // Store the auth details
  79. addAuthSession(self.authStore, new AuthSession(db, username, password, options));
  80. // Return correct authentication
  81. callback(null, true);
  82. } else if(count == 0) {
  83. if(errorObject == null) errorObject = new MongoError(f("failed to authenticate using mongocr"));
  84. callback(errorObject, false);
  85. }
  86. });
  87. }
  88. var _execute = function(_connection) {
  89. process.nextTick(function() {
  90. execute(_connection);
  91. });
  92. }
  93. _execute(connections.shift());
  94. }
  95. }
  96. //
  97. // Initialize step
  98. var GSSAPIInitialize = function(self, db, username, password, authdb, gssapiServiceName, server, connection, options, callback) {
  99. // Create authenticator
  100. var mongo_auth_process = new MongoAuthProcess(connection.host, connection.port, gssapiServiceName, options);
  101. // Perform initialization
  102. mongo_auth_process.init(username, password, function(err) {
  103. if(err) return callback(err, false);
  104. // Perform the first step
  105. mongo_auth_process.transition('', function(err, payload) {
  106. if(err) return callback(err, false);
  107. // Call the next db step
  108. MongoDBGSSAPIFirstStep(self, mongo_auth_process, payload, db, username, password, authdb, server, connection, callback);
  109. });
  110. });
  111. }
  112. //
  113. // Perform first step against mongodb
  114. var MongoDBGSSAPIFirstStep = function(self, mongo_auth_process, payload, db, username, password, authdb, server, connection, callback) {
  115. // Build the sasl start command
  116. var command = {
  117. saslStart: 1
  118. , mechanism: 'GSSAPI'
  119. , payload: payload
  120. , autoAuthorize: 1
  121. };
  122. // Write the commmand on the connection
  123. server(connection, new Query(self.bson, "$external.$cmd", command, {
  124. numberToSkip: 0, numberToReturn: 1
  125. }), function(err, r) {
  126. if(err) return callback(err, false);
  127. var doc = r.result;
  128. // Execute mongodb transition
  129. mongo_auth_process.transition(r.result.payload, function(err, payload) {
  130. if(err) return callback(err, false);
  131. // MongoDB API Second Step
  132. MongoDBGSSAPISecondStep(self, mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback);
  133. });
  134. });
  135. }
  136. //
  137. // Perform first step against mongodb
  138. var MongoDBGSSAPISecondStep = function(self, mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback) {
  139. // Build Authentication command to send to MongoDB
  140. var command = {
  141. saslContinue: 1
  142. , conversationId: doc.conversationId
  143. , payload: payload
  144. };
  145. // Execute the command
  146. // Write the commmand on the connection
  147. server(connection, new Query(self.bson, "$external.$cmd", command, {
  148. numberToSkip: 0, numberToReturn: 1
  149. }), function(err, r) {
  150. if(err) return callback(err, false);
  151. var doc = r.result;
  152. // Call next transition for kerberos
  153. mongo_auth_process.transition(doc.payload, function(err, payload) {
  154. if(err) return callback(err, false);
  155. // Call the last and third step
  156. MongoDBGSSAPIThirdStep(self, mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback);
  157. });
  158. });
  159. }
  160. var MongoDBGSSAPIThirdStep = function(self, mongo_auth_process, payload, doc, db, username, password, authdb, server, connection, callback) {
  161. // Build final command
  162. var command = {
  163. saslContinue: 1
  164. , conversationId: doc.conversationId
  165. , payload: payload
  166. };
  167. // Execute the command
  168. server(connection, new Query(self.bson, "$external.$cmd", command, {
  169. numberToSkip: 0, numberToReturn: 1
  170. }), function(err, r) {
  171. if(err) return callback(err, false);
  172. mongo_auth_process.transition(null, function(err) {
  173. if(err) return callback(err, null);
  174. callback(null, r);
  175. });
  176. });
  177. }
  178. // Add to store only if it does not exist
  179. var addAuthSession = function(authStore, session) {
  180. var found = false;
  181. for(var i = 0; i < authStore.length; i++) {
  182. if(authStore[i].equal(session)) {
  183. found = true;
  184. break;
  185. }
  186. }
  187. if(!found) authStore.push(session);
  188. }
  189. /**
  190. * Remove authStore credentials
  191. * @method
  192. * @param {string} db Name of database we are removing authStore details about
  193. * @return {object}
  194. */
  195. GSSAPI.prototype.logout = function(dbName) {
  196. this.authStore = this.authStore.filter(function(x) {
  197. return x.db != dbName;
  198. });
  199. }
  200. /**
  201. * Re authenticate pool
  202. * @method
  203. * @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
  204. * @param {[]Connections} connections Connections to authenticate using this authenticator
  205. * @param {authResultCallback} callback The callback to return the result from the authentication
  206. * @return {object}
  207. */
  208. GSSAPI.prototype.reauthenticate = function(server, connections, callback) {
  209. var authStore = this.authStore.slice(0);
  210. var count = authStore.length;
  211. if(count == 0) return callback(null, null);
  212. // Iterate over all the auth details stored
  213. for(var i = 0; i < authStore.length; i++) {
  214. this.auth(server, connections, authStore[i].db, authStore[i].username, authStore[i].password, authStore[i].options, function(err) {
  215. count = count - 1;
  216. // Done re-authenticating
  217. if(count == 0) {
  218. callback(err, null);
  219. }
  220. });
  221. }
  222. }
  223. /**
  224. * This is a result from a authentication strategy
  225. *
  226. * @callback authResultCallback
  227. * @param {error} error An error object. Set to null if no error present
  228. * @param {boolean} result The result of the authentication process
  229. */
  230. module.exports = GSSAPI;