01be1bf1e306b72e6042836de1f14644f4fd251d
[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 SSPI authentication mechanism
35  * @class
36  * @return {SSPI} A cursor instance
37  */
38 var SSPI = 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 SSPI.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       SSIPAuthenticate(self, username, password, 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 && typeof r == 'object' && r.result['$err']) {
81           errorObject = r.result;
82         } else if(r && typeof r == 'object' && 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 var SSIPAuthenticate = function(self, username, password, gssapiServiceName, server, connection, options, callback) {
113   // Build Authentication command to send to MongoDB
114   var command = {
115       saslStart: 1
116     , mechanism: 'GSSAPI'
117     , payload: ''
118     , autoAuthorize: 1
119   };
120
121   // Create authenticator
122   var mongo_auth_process = new MongoAuthProcess(connection.host, connection.port, gssapiServiceName, options);
123
124   // Execute first sasl step
125   server(connection, new Query(self.bson, "$external.$cmd", command, {
126     numberToSkip: 0, numberToReturn: 1
127   }), function(err, r) {
128     if(err) return callback(err, false);
129     var doc = r.result;
130
131     mongo_auth_process.init(username, password, function(err) {
132       if(err) return callback(err);
133
134       mongo_auth_process.transition(doc.payload, function(err, payload) {
135         if(err) return callback(err);
136
137         // Perform the next step against mongod
138         var command = {
139             saslContinue: 1
140           , conversationId: doc.conversationId
141           , payload: payload
142         };
143
144         // Execute the command
145         server(connection, new Query(self.bson, "$external.$cmd", command, {
146           numberToSkip: 0, numberToReturn: 1
147         }), function(err, r) {
148           if(err) return callback(err, false);
149           var doc = r.result;
150
151           mongo_auth_process.transition(doc.payload, function(err, payload) {
152             if(err) return callback(err);
153
154             // Perform the next step against mongod
155             var command = {
156                 saslContinue: 1
157               , conversationId: doc.conversationId
158               , payload: payload
159             };
160
161             // Execute the command
162             server(connection, new Query(self.bson, "$external.$cmd", command, {
163               numberToSkip: 0, numberToReturn: 1
164             }), function(err, r) {
165               if(err) return callback(err, false);
166               var doc = r.result;
167
168               mongo_auth_process.transition(doc.payload, function(err, payload) {
169                 // Perform the next step against mongod
170                 var command = {
171                     saslContinue: 1
172                   , conversationId: doc.conversationId
173                   , payload: payload
174                 };
175
176                 // Execute the command
177                 server(connection, new Query(self.bson, "$external.$cmd", command, {
178                   numberToSkip: 0, numberToReturn: 1
179                 }), function(err, r) {
180                   if(err) return callback(err, false);
181                   var doc = r.result;
182
183                   if(doc.done) return callback(null, true);
184                   callback(new Error("Authentication failed"), false);
185                 });
186               });
187             });
188           });
189         });
190       });
191     });
192   });
193 }
194
195 // Add to store only if it does not exist
196 var addAuthSession = function(authStore, session) {
197   var found = false;
198
199   for(var i = 0; i < authStore.length; i++) {
200     if(authStore[i].equal(session)) {
201       found = true;
202       break;
203     }
204   }
205
206   if(!found) authStore.push(session);
207 }
208
209 /**
210  * Remove authStore credentials
211  * @method
212  * @param {string} db Name of database we are removing authStore details about
213  * @return {object}
214  */
215 SSPI.prototype.logout = function(dbName) {
216   this.authStore = this.authStore.filter(function(x) {
217     return x.db != dbName;
218   });
219 }
220
221 /**
222  * Re authenticate pool
223  * @method
224  * @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
225  * @param {[]Connections} connections Connections to authenticate using this authenticator
226  * @param {authResultCallback} callback The callback to return the result from the authentication
227  * @return {object}
228  */
229 SSPI.prototype.reauthenticate = function(server, connections, callback) {
230   var authStore = this.authStore.slice(0);
231   var err = null;
232   var count = authStore.length;
233   if(count == 0) return callback(null, null);
234   // Iterate over all the auth details stored
235   for(var i = 0; i < authStore.length; i++) {
236     this.auth(server, connections, authStore[i].db, authStore[i].username, authStore[i].password, authStore[i].options, function(err, r) {
237       if(err) err = err;
238       count = count - 1;
239       // Done re-authenticating
240       if(count == 0) {
241         callback(err, null);
242       }
243     });
244   }
245 }
246
247 /**
248  * This is a result from a authentication strategy
249  *
250  * @callback authResultCallback
251  * @param {error} error An error object. Set to null if no error present
252  * @param {boolean} result The result of the authentication process
253  */
254
255 module.exports = SSPI;