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