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