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