Update Node_modules

This commit is contained in:
2019-07-02 16:05:15 +02:00
parent 95a0ff3913
commit 9dab5e2dbc
3495 changed files with 39728 additions and 465154 deletions
+158
View File
@@ -0,0 +1,158 @@
'use strict';
const MongoError = require('../error').MongoError;
/**
* Creates a new AuthProvider, which dictates how to authenticate for a given
* mechanism.
* @class
*/
class AuthProvider {
constructor(bson) {
this.bson = bson;
this.authStore = [];
}
/**
* Authenticate
* @method
* @param {SendAuthCommand} sendAuthCommand Writes an auth command directly to a specific connection
* @param {Connection[]} connections Connections to authenticate using this authenticator
* @param {MongoCredentials} credentials Authentication credentials
* @param {authResultCallback} callback The callback to return the result from the authentication
*/
auth(sendAuthCommand, connections, credentials, callback) {
// Total connections
let count = connections.length;
if (count === 0) {
callback(null, null);
return;
}
// Valid connections
let numberOfValidConnections = 0;
let errorObject = null;
const execute = connection => {
this._authenticateSingleConnection(sendAuthCommand, connection, credentials, (err, r) => {
// Adjust count
count = count - 1;
// If we have an error
if (err) {
errorObject = new MongoError(err);
} else if (r && (r.$err || r.errmsg)) {
errorObject = new MongoError(r);
} else {
numberOfValidConnections = numberOfValidConnections + 1;
}
// Still authenticating against other connections.
if (count !== 0) {
return;
}
// We have authenticated all connections
if (numberOfValidConnections > 0) {
// Store the auth details
this.addCredentials(credentials);
// Return correct authentication
callback(null, true);
} else {
if (errorObject == null) {
errorObject = new MongoError(`failed to authenticate using ${credentials.mechanism}`);
}
callback(errorObject, false);
}
});
};
const executeInNextTick = _connection => process.nextTick(() => execute(_connection));
// For each connection we need to authenticate
while (connections.length > 0) {
executeInNextTick(connections.shift());
}
}
/**
* Implementation of a single connection authenticating. Is meant to be overridden.
* Will error if called directly
* @ignore
*/
_authenticateSingleConnection(/*sendAuthCommand, connection, credentials, callback*/) {
throw new Error('_authenticateSingleConnection must be overridden');
}
/**
* Adds credentials to store only if it does not exist
* @param {MongoCredentials} credentials credentials to add to store
*/
addCredentials(credentials) {
const found = this.authStore.some(cred => cred.equals(credentials));
if (!found) {
this.authStore.push(credentials);
}
}
/**
* Re authenticate pool
* @method
* @param {SendAuthCommand} sendAuthCommand Writes an auth command directly to a specific connection
* @param {Connection[]} connections Connections to authenticate using this authenticator
* @param {authResultCallback} callback The callback to return the result from the authentication
*/
reauthenticate(sendAuthCommand, connections, callback) {
const authStore = this.authStore.slice(0);
let count = authStore.length;
if (count === 0) {
return callback(null, null);
}
for (let i = 0; i < authStore.length; i++) {
this.auth(sendAuthCommand, connections, authStore[i], function(err) {
count = count - 1;
if (count === 0) {
callback(err, null);
}
});
}
}
/**
* Remove credentials that have been previously stored in the auth provider
* @method
* @param {string} source Name of database we are removing authStore details about
* @return {object}
*/
logout(source) {
this.authStore = this.authStore.filter(credentials => credentials.source !== source);
}
}
/**
* A function that writes authentication commands to a specific connection
* @callback SendAuthCommand
* @param {Connection} connection The connection to write to
* @param {Command} command A command with a toBin method that can be written to a connection
* @param {AuthWriteCallback} callback Callback called when command response is received
*/
/**
* A callback for a specific auth command
* @callback AuthWriteCallback
* @param {Error} err If command failed, an error from the server
* @param {object} r The response from the server
*/
/**
* This is a result from an authentication strategy
*
* @callback authResultCallback
* @param {error} error An error object. Set to null if no error present
* @param {boolean} result The result of the authentication process
*/
module.exports = { AuthProvider };
+96 -236
View File
@@ -1,120 +1,61 @@
'use strict';
const f = require('util').format;
const Query = require('../connection/commands').Query;
const MongoError = require('../error').MongoError;
const AuthProvider = require('./auth_provider').AuthProvider;
const retrieveKerberos = require('../utils').retrieveKerberos;
var AuthSession = function(db, username, password, options) {
this.db = db;
this.username = username;
this.password = password;
this.options = options;
};
AuthSession.prototype.equal = function(session) {
return (
session.db === this.db &&
session.username === this.username &&
session.password === this.password
);
};
let kerberos;
/**
* Creates a new GSSAPI authentication mechanism
* @class
* @return {GSSAPI} A cursor instance
* @extends AuthProvider
*/
var GSSAPI = function(bson) {
this.bson = bson;
this.authStore = [];
};
class GSSAPI extends AuthProvider {
/**
* Implementation of authentication for a single connection
* @override
*/
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
const source = credentials.source;
const username = credentials.username;
const password = credentials.password;
const mechanismProperties = credentials.mechanismProperties;
const gssapiServiceName =
mechanismProperties['gssapiservicename'] ||
mechanismProperties['gssapiServiceName'] ||
'mongodb';
/**
* Authenticate
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {string} db Name of the database
* @param {string} username Username
* @param {string} password Password
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
GSSAPI.prototype.auth = function(server, connections, db, username, password, options, callback) {
var self = this;
let kerberos;
try {
kerberos = retrieveKerberos();
} catch (e) {
return callback(e, null);
GSSAPIInitialize(
this,
kerberos.processes.MongoAuthProcess,
source,
username,
password,
source,
gssapiServiceName,
sendAuthCommand,
connection,
mechanismProperties,
callback
);
}
// TODO: remove this once we fix URI parsing
var gssapiServiceName = options['gssapiservicename'] || options['gssapiServiceName'] || 'mongodb';
// Total connections
var count = connections.length;
if (count === 0) return callback(null, null);
/**
* Authenticate
* @override
* @method
*/
auth(sendAuthCommand, connections, credentials, callback) {
if (kerberos == null) {
try {
kerberos = retrieveKerberos();
} catch (e) {
return callback(e, null);
}
}
// Valid connections
var numberOfValidConnections = 0;
var errorObject = null;
// For each connection we need to authenticate
while (connections.length > 0) {
// Execute MongoCR
var execute = function(connection) {
// Start Auth process for a connection
GSSAPIInitialize(
self,
kerberos.processes.MongoAuthProcess,
db,
username,
password,
db,
gssapiServiceName,
server,
connection,
options,
function(err, r) {
// Adjust count
count = count - 1;
// If we have an error
if (err) {
errorObject = err;
} else if (r.result['$err']) {
errorObject = r.result;
} else if (r.result['errmsg']) {
errorObject = r.result;
} else {
numberOfValidConnections = numberOfValidConnections + 1;
}
// We have authenticated all connections
if (count === 0 && numberOfValidConnections > 0) {
// Store the auth details
addAuthSession(self.authStore, new AuthSession(db, username, password, options));
// Return correct authentication
callback(null, true);
} else if (count === 0) {
if (errorObject == null)
errorObject = new MongoError(f('failed to authenticate using mongocr'));
callback(errorObject, false);
}
}
);
};
var _execute = function(_connection) {
process.nextTick(function() {
execute(_connection);
});
};
_execute(connections.shift());
super.auth(sendAuthCommand, connections, credentials, callback);
}
};
}
//
// Initialize step
@@ -126,7 +67,7 @@ var GSSAPIInitialize = function(
password,
authdb,
gssapiServiceName,
server,
sendAuthCommand,
connection,
options,
callback
@@ -156,7 +97,7 @@ var GSSAPIInitialize = function(
username,
password,
authdb,
server,
sendAuthCommand,
connection,
callback
);
@@ -174,7 +115,7 @@ var MongoDBGSSAPIFirstStep = function(
username,
password,
authdb,
server,
sendAuthCommand,
connection,
callback
) {
@@ -187,36 +128,28 @@ var MongoDBGSSAPIFirstStep = function(
};
// Write the commmand on the connection
server(
connection,
new Query(self.bson, '$external.$cmd', command, {
numberToSkip: 0,
numberToReturn: 1
}),
function(err, r) {
sendAuthCommand(connection, '$external.$cmd', command, (err, doc) => {
if (err) return callback(err, false);
// Execute mongodb transition
mongo_auth_process.transition(doc.payload, function(err, payload) {
if (err) return callback(err, false);
var doc = r.result;
// Execute mongodb transition
mongo_auth_process.transition(r.result.payload, function(err, payload) {
if (err) return callback(err, false);
// MongoDB API Second Step
MongoDBGSSAPISecondStep(
self,
mongo_auth_process,
payload,
doc,
db,
username,
password,
authdb,
server,
connection,
callback
);
});
}
);
// MongoDB API Second Step
MongoDBGSSAPISecondStep(
self,
mongo_auth_process,
payload,
doc,
db,
username,
password,
authdb,
sendAuthCommand,
connection,
callback
);
});
});
};
//
@@ -230,7 +163,7 @@ var MongoDBGSSAPISecondStep = function(
username,
password,
authdb,
server,
sendAuthCommand,
connection,
callback
) {
@@ -243,36 +176,28 @@ var MongoDBGSSAPISecondStep = function(
// Execute the command
// Write the commmand on the connection
server(
connection,
new Query(self.bson, '$external.$cmd', command, {
numberToSkip: 0,
numberToReturn: 1
}),
function(err, r) {
sendAuthCommand(connection, '$external.$cmd', command, (err, doc) => {
if (err) return callback(err, false);
// Call next transition for kerberos
mongo_auth_process.transition(doc.payload, function(err, payload) {
if (err) return callback(err, false);
var doc = r.result;
// Call next transition for kerberos
mongo_auth_process.transition(doc.payload, function(err, payload) {
if (err) return callback(err, false);
// Call the last and third step
MongoDBGSSAPIThirdStep(
self,
mongo_auth_process,
payload,
doc,
db,
username,
password,
authdb,
server,
connection,
callback
);
});
}
);
// Call the last and third step
MongoDBGSSAPIThirdStep(
self,
mongo_auth_process,
payload,
doc,
db,
username,
password,
authdb,
sendAuthCommand,
connection,
callback
);
});
});
};
var MongoDBGSSAPIThirdStep = function(
@@ -284,7 +209,7 @@ var MongoDBGSSAPIThirdStep = function(
username,
password,
authdb,
server,
sendAuthCommand,
connection,
callback
) {
@@ -296,80 +221,15 @@ var MongoDBGSSAPIThirdStep = function(
};
// Execute the command
server(
connection,
new Query(self.bson, '$external.$cmd', command, {
numberToSkip: 0,
numberToReturn: 1
}),
function(err, r) {
if (err) return callback(err, false);
mongo_auth_process.transition(null, function(err) {
if (err) return callback(err, null);
callback(null, r);
});
}
);
};
// Add to store only if it does not exist
var addAuthSession = function(authStore, session) {
var found = false;
for (var i = 0; i < authStore.length; i++) {
if (authStore[i].equal(session)) {
found = true;
break;
}
}
if (!found) authStore.push(session);
};
/**
* Remove authStore credentials
* @method
* @param {string} db Name of database we are removing authStore details about
* @return {object}
*/
GSSAPI.prototype.logout = function(dbName) {
this.authStore = this.authStore.filter(function(x) {
return x.db !== dbName;
sendAuthCommand(connection, '$external.$cmd', command, (err, r) => {
if (err) return callback(err, false);
mongo_auth_process.transition(null, function(err) {
if (err) return callback(err, null);
callback(null, r);
});
});
};
/**
* Re authenticate pool
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
GSSAPI.prototype.reauthenticate = function(server, connections, callback) {
var authStore = this.authStore.slice(0);
var count = authStore.length;
if (count === 0) return callback(null, null);
// Iterate over all the auth details stored
for (var i = 0; i < authStore.length; i++) {
this.auth(
server,
connections,
authStore[i].db,
authStore[i].username,
authStore[i].password,
authStore[i].options,
function(err) {
count = count - 1;
// Done re-authenticating
if (count === 0) {
callback(err, null);
}
}
);
}
};
/**
* This is a result from a authentication strategy
*
+81
View File
@@ -0,0 +1,81 @@
'use strict';
// Resolves the default auth mechanism according to
// https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst
function getDefaultAuthMechanism(ismaster) {
if (ismaster) {
// If ismaster contains saslSupportedMechs, use scram-sha-256
// if it is available, else scram-sha-1
if (Array.isArray(ismaster.saslSupportedMechs)) {
return ismaster.saslSupportedMechs.indexOf('SCRAM-SHA-256') >= 0
? 'scram-sha-256'
: 'scram-sha-1';
}
// Fallback to legacy selection method. If wire version >= 3, use scram-sha-1
if (ismaster.maxWireVersion >= 3) {
return 'scram-sha-1';
}
}
// Default for wireprotocol < 3
return 'mongocr';
}
/**
* A representation of the credentials used by MongoDB
* @class
* @property {string} mechanism The method used to authenticate
* @property {string} [username] The username used for authentication
* @property {string} [password] The password used for authentication
* @property {string} [source] The database that the user should authenticate against
* @property {object} [mechanismProperties] Special properties used by some types of auth mechanisms
*/
class MongoCredentials {
/**
* Creates a new MongoCredentials object
* @param {object} [options]
* @param {string} [options.username] The username used for authentication
* @param {string} [options.password] The password used for authentication
* @param {string} [options.source] The database that the user should authenticate against
* @param {string} [options.mechanism] The method used to authenticate
* @param {object} [options.mechanismProperties] Special properties used by some types of auth mechanisms
*/
constructor(options) {
options = options || {};
this.username = options.username;
this.password = options.password;
this.source = options.source || options.db;
this.mechanism = options.mechanism || 'default';
this.mechanismProperties = options.mechanismProperties;
}
/**
* Determines if two MongoCredentials objects are equivalent
* @param {MongoCredentials} other another MongoCredentials object
* @returns {boolean} true if the two objects are equal.
*/
equals(other) {
return (
this.mechanism === other.mechanism &&
this.username === other.username &&
this.password === other.password &&
this.source === other.source
);
}
/**
* If the authentication mechanism is set to "default", resolves the authMechanism
* based on the server version and server supported sasl mechanisms.
*
* @param {Object} [ismaster] An ismaster response from the server
*/
resolveAuthMechanism(ismaster) {
// If the mechanism is not "default", then it does not need to be resolved
if (this.mechanism.toLowerCase() === 'default') {
this.mechanism = getDefaultAuthMechanism(ismaster);
}
}
}
module.exports = { MongoCredentials };
+42 -205
View File
@@ -1,214 +1,51 @@
'use strict';
var f = require('util').format,
crypto = require('crypto'),
Query = require('../connection/commands').Query,
MongoError = require('../error').MongoError;
var AuthSession = function(db, username, password) {
this.db = db;
this.username = username;
this.password = password;
};
AuthSession.prototype.equal = function(session) {
return (
session.db === this.db &&
session.username === this.username &&
session.password === this.password
);
};
const crypto = require('crypto');
const AuthProvider = require('./auth_provider').AuthProvider;
/**
* Creates a new MongoCR authentication mechanism
* @class
* @return {MongoCR} A cursor instance
*/
var MongoCR = function(bson) {
this.bson = bson;
this.authStore = [];
};
// Add to store only if it does not exist
var addAuthSession = function(authStore, session) {
var found = false;
for (var i = 0; i < authStore.length; i++) {
if (authStore[i].equal(session)) {
found = true;
break;
}
}
if (!found) authStore.push(session);
};
/**
* Authenticate
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {string} db Name of the database
* @param {string} username Username
* @param {string} password Password
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
MongoCR.prototype.auth = function(server, connections, db, username, password, callback) {
var self = this;
// Total connections
var count = connections.length;
if (count === 0) return callback(null, null);
// Valid connections
var numberOfValidConnections = 0;
var errorObject = null;
// For each connection we need to authenticate
while (connections.length > 0) {
// Execute MongoCR
var executeMongoCR = function(connection) {
// Write the commmand on the connection
server(
connection,
new Query(
self.bson,
f('%s.$cmd', db),
{
getnonce: 1
},
{
numberToSkip: 0,
numberToReturn: 1
}
),
function(err, r) {
var nonce = null;
var key = null;
// Adjust the number of connections left
// Get nonce
if (err == null) {
nonce = r.result.nonce;
// Use node md5 generator
var md5 = crypto.createHash('md5');
// Generate keys used for authentication
md5.update(username + ':mongo:' + password, 'utf8');
var hash_password = md5.digest('hex');
// Final key
md5 = crypto.createHash('md5');
md5.update(nonce + username + hash_password, 'utf8');
key = md5.digest('hex');
}
// Execute command
// Write the commmand on the connection
server(
connection,
new Query(
self.bson,
f('%s.$cmd', db),
{
authenticate: 1,
user: username,
nonce: nonce,
key: key
},
{
numberToSkip: 0,
numberToReturn: 1
}
),
function(err, r) {
count = count - 1;
// If we have an error
if (err) {
errorObject = err;
} else if (r.result['$err']) {
errorObject = r.result;
} else if (r.result['errmsg']) {
errorObject = r.result;
} else {
numberOfValidConnections = numberOfValidConnections + 1;
}
// We have authenticated all connections
if (count === 0 && numberOfValidConnections > 0) {
// Store the auth details
addAuthSession(self.authStore, new AuthSession(db, username, password));
// Return correct authentication
callback(null, true);
} else if (count === 0) {
if (errorObject == null)
errorObject = new MongoError(f('failed to authenticate using mongocr'));
callback(errorObject, false);
}
}
);
}
);
};
var _execute = function(_connection) {
process.nextTick(function() {
executeMongoCR(_connection);
});
};
_execute(connections.shift());
}
};
/**
* Remove authStore credentials
* @method
* @param {string} db Name of database we are removing authStore details about
* @return {object}
*/
MongoCR.prototype.logout = function(dbName) {
this.authStore = this.authStore.filter(function(x) {
return x.db !== dbName;
});
};
/**
* Re authenticate pool
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
MongoCR.prototype.reauthenticate = function(server, connections, callback) {
var authStore = this.authStore.slice(0);
var count = authStore.length;
if (count === 0) return callback(null, null);
// Iterate over all the auth details stored
for (var i = 0; i < authStore.length; i++) {
this.auth(
server,
connections,
authStore[i].db,
authStore[i].username,
authStore[i].password,
function(err) {
count = count - 1;
// Done re-authenticating
if (count === 0) {
callback(err, null);
}
}
);
}
};
/**
* This is a result from a authentication strategy
*
* @callback authResultCallback
* @param {error} error An error object. Set to null if no error present
* @param {boolean} result The result of the authentication process
* @extends AuthProvider
*/
class MongoCR extends AuthProvider {
/**
* Implementation of authentication for a single connection
* @override
*/
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
const username = credentials.username;
const password = credentials.password;
const source = credentials.source;
sendAuthCommand(connection, `${source}.$cmd`, { getnonce: 1 }, (err, r) => {
let nonce = null;
let key = null;
// Get nonce
if (err == null) {
nonce = r.nonce;
// Use node md5 generator
let md5 = crypto.createHash('md5');
// Generate keys used for authentication
md5.update(username + ':mongo:' + password, 'utf8');
const hash_password = md5.digest('hex');
// Final key
md5 = crypto.createHash('md5');
md5.update(nonce + username + hash_password, 'utf8');
key = md5.digest('hex');
}
const authenticateCommand = {
authenticate: 1,
user: username,
nonce,
key
};
sendAuthCommand(connection, `${source}.$cmd`, authenticateCommand, callback);
});
}
}
module.exports = MongoCR;
+25 -173
View File
@@ -1,183 +1,35 @@
'use strict';
var f = require('util').format,
retrieveBSON = require('../connection/utils').retrieveBSON,
Query = require('../connection/commands').Query,
MongoError = require('../error').MongoError;
const retrieveBSON = require('../connection/utils').retrieveBSON;
const AuthProvider = require('./auth_provider').AuthProvider;
var BSON = retrieveBSON(),
Binary = BSON.Binary;
var AuthSession = function(db, username, password) {
this.db = db;
this.username = username;
this.password = password;
};
AuthSession.prototype.equal = function(session) {
return (
session.db === this.db &&
session.username === this.username &&
session.password === this.password
);
};
// TODO: can we get the Binary type from this.bson instead?
const BSON = retrieveBSON();
const Binary = BSON.Binary;
/**
* Creates a new Plain authentication mechanism
* @class
* @return {Plain} A cursor instance
*/
var Plain = function(bson) {
this.bson = bson;
this.authStore = [];
};
/**
* Authenticate
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {string} db Name of the database
* @param {string} username Username
* @param {string} password Password
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
Plain.prototype.auth = function(server, connections, db, username, password, callback) {
var self = this;
// Total connections
var count = connections.length;
if (count === 0) return callback(null, null);
// Valid connections
var numberOfValidConnections = 0;
var errorObject = null;
// For each connection we need to authenticate
while (connections.length > 0) {
// Execute MongoCR
var execute = function(connection) {
// Create payload
var payload = new Binary(f('\x00%s\x00%s', username, password));
// Let's start the sasl process
var command = {
saslStart: 1,
mechanism: 'PLAIN',
payload: payload,
autoAuthorize: 1
};
// Let's start the process
server(
connection,
new Query(self.bson, '$external.$cmd', command, {
numberToSkip: 0,
numberToReturn: 1
}),
function(err, r) {
// Adjust count
count = count - 1;
// If we have an error
if (err) {
errorObject = err;
} else if (r.result['$err']) {
errorObject = r.result;
} else if (r.result['errmsg']) {
errorObject = r.result;
} else {
numberOfValidConnections = numberOfValidConnections + 1;
}
// We have authenticated all connections
if (count === 0 && numberOfValidConnections > 0) {
// Store the auth details
addAuthSession(self.authStore, new AuthSession(db, username, password));
// Return correct authentication
callback(null, true);
} else if (count === 0) {
if (errorObject == null)
errorObject = new MongoError(f('failed to authenticate using mongocr'));
callback(errorObject, false);
}
}
);
};
var _execute = function(_connection) {
process.nextTick(function() {
execute(_connection);
});
};
_execute(connections.shift());
}
};
// Add to store only if it does not exist
var addAuthSession = function(authStore, session) {
var found = false;
for (var i = 0; i < authStore.length; i++) {
if (authStore[i].equal(session)) {
found = true;
break;
}
}
if (!found) authStore.push(session);
};
/**
* Remove authStore credentials
* @method
* @param {string} db Name of database we are removing authStore details about
* @return {object}
*/
Plain.prototype.logout = function(dbName) {
this.authStore = this.authStore.filter(function(x) {
return x.db !== dbName;
});
};
/**
* Re authenticate pool
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
Plain.prototype.reauthenticate = function(server, connections, callback) {
var authStore = this.authStore.slice(0);
var count = authStore.length;
if (count === 0) return callback(null, null);
// Iterate over all the auth details stored
for (var i = 0; i < authStore.length; i++) {
this.auth(
server,
connections,
authStore[i].db,
authStore[i].username,
authStore[i].password,
function(err) {
count = count - 1;
// Done re-authenticating
if (count === 0) {
callback(err, null);
}
}
);
}
};
/**
* This is a result from a authentication strategy
*
* @callback authResultCallback
* @param {error} error An error object. Set to null if no error present
* @param {boolean} result The result of the authentication process
* @extends AuthProvider
*/
class Plain extends AuthProvider {
/**
* Implementation of authentication for a single connection
* @override
*/
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
const username = credentials.username;
const password = credentials.password;
const payload = new Binary(`\x00${username}\x00${password}`);
const command = {
saslStart: 1,
mechanism: 'PLAIN',
payload: payload,
autoAuthorize: 1
};
sendAuthCommand(connection, '$external.$cmd', command, callback);
}
}
module.exports = Plain;
+142 -291
View File
@@ -1,51 +1,21 @@
'use strict';
var f = require('util').format,
crypto = require('crypto'),
retrieveBSON = require('../connection/utils').retrieveBSON,
Query = require('../connection/commands').Query,
MongoError = require('../error').MongoError,
Buffer = require('safe-buffer').Buffer;
const crypto = require('crypto');
const Buffer = require('safe-buffer').Buffer;
const retrieveBSON = require('../connection/utils').retrieveBSON;
const MongoError = require('../error').MongoError;
const AuthProvider = require('./auth_provider').AuthProvider;
const BSON = retrieveBSON();
const Binary = BSON.Binary;
let saslprep;
try {
saslprep = require('saslprep');
} catch (e) {
// don't do anything;
}
var BSON = retrieveBSON(),
Binary = BSON.Binary;
var AuthSession = function(db, username, password) {
this.db = db;
this.username = username;
this.password = password;
};
AuthSession.prototype.equal = function(session) {
return (
session.db === this.db &&
session.username === this.username &&
session.password === this.password
);
};
var id = 0;
/**
* Creates a new ScramSHA authentication mechanism
* @class
* @return {ScramSHA} A cursor instance
*/
var ScramSHA = function(bson, cryptoMethod) {
this.bson = bson;
this.authStore = [];
this.id = id++;
this.cryptoMethod = cryptoMethod || 'sha1';
};
var parsePayload = function(payload) {
var dict = {};
var parts = payload.split(',');
@@ -136,54 +106,53 @@ function HI(data, salt, iterations, cryptoMethod) {
}
/**
* Authenticate
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {string} db Name of the database
* @param {string} username Username
* @param {string} password Password
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
* Creates a new ScramSHA authentication mechanism
* @class
* @extends AuthProvider
*/
ScramSHA.prototype.auth = function(server, connections, db, username, password, callback) {
var self = this;
// Total connections
var count = connections.length;
if (count === 0) return callback(null, null);
// Valid connections
var numberOfValidConnections = 0;
var errorObject = null;
const cryptoMethod = this.cryptoMethod;
let mechanism = 'SCRAM-SHA-1';
let processedPassword;
if (cryptoMethod === 'sha256') {
mechanism = 'SCRAM-SHA-256';
let saslprepFn = (server.s && server.s.saslprep) || saslprep;
if (saslprepFn) {
processedPassword = saslprepFn(password);
} else {
console.warn('Warning: no saslprep library specified. Passwords will not be sanitized');
processedPassword = password;
}
} else {
processedPassword = passwordDigest(username, password);
class ScramSHA extends AuthProvider {
constructor(bson, cryptoMethod) {
super(bson);
this.cryptoMethod = cryptoMethod || 'sha1';
}
// Execute MongoCR
var executeScram = function(connection) {
static _getError(err, r) {
if (err) {
return err;
}
if (r.$err || r.errmsg) {
return new MongoError(r);
}
}
/**
* @ignore
*/
_executeScram(sendAuthCommand, connection, credentials, nonce, callback) {
let username = credentials.username;
const password = credentials.password;
const db = credentials.source;
const cryptoMethod = this.cryptoMethod;
let mechanism = 'SCRAM-SHA-1';
let processedPassword;
if (cryptoMethod === 'sha256') {
mechanism = 'SCRAM-SHA-256';
processedPassword = saslprep ? saslprep(password) : password;
} else {
try {
processedPassword = passwordDigest(username, password);
} catch (e) {
return callback(e);
}
}
// Clean up the user
username = username.replace('=', '=3D').replace(',', '=2C');
// Create a random nonce
var nonce = crypto.randomBytes(24).toString('base64');
// var nonce = 'MsQUY9iw0T9fx2MUEz6LZPwGuhVvWAhc'
// NOTE: This is done b/c Javascript uses UTF-16, but the server is hashing in UTF-8.
// Since the username is not sasl-prep-d, we need to do this here.
const firstBare = Buffer.concat([
@@ -194,245 +163,127 @@ ScramSHA.prototype.auth = function(server, connections, db, username, password,
]);
// Build command structure
var cmd = {
const saslStartCmd = {
saslStart: 1,
mechanism: mechanism,
mechanism,
payload: new Binary(Buffer.concat([Buffer.from('n,,', 'utf8'), firstBare])),
autoAuthorize: 1
};
// Handle the error
var handleError = function(err, r) {
if (err) {
numberOfValidConnections = numberOfValidConnections - 1;
errorObject = err;
return false;
} else if (r.result['$err']) {
errorObject = r.result;
return false;
} else if (r.result['errmsg']) {
errorObject = r.result;
return false;
} else {
numberOfValidConnections = numberOfValidConnections + 1;
}
return true;
};
// Finish up
var finish = function(_count, _numberOfValidConnections) {
if (_count === 0 && _numberOfValidConnections > 0) {
// Store the auth details
addAuthSession(self.authStore, new AuthSession(db, username, password));
// Return correct authentication
return callback(null, true);
} else if (_count === 0) {
if (errorObject == null)
errorObject = new MongoError(f('failed to authenticate using scram'));
return callback(errorObject, false);
}
};
var handleEnd = function(_err, _r) {
// Handle any error
handleError(_err, _r);
// Adjust the number of connections
count = count - 1;
// Execute the finish
finish(count, numberOfValidConnections);
};
// Write the commmand on the connection
server(
connection,
new Query(self.bson, f('%s.$cmd', db), cmd, {
numberToSkip: 0,
numberToReturn: 1
}),
function(err, r) {
// Do we have an error, handle it
if (handleError(err, r) === false) {
count = count - 1;
sendAuthCommand(connection, `${db}.$cmd`, saslStartCmd, (err, r) => {
let tmpError = ScramSHA._getError(err, r);
if (tmpError) {
return callback(tmpError, null);
}
if (count === 0 && numberOfValidConnections > 0) {
// Store the auth details
addAuthSession(self.authStore, new AuthSession(db, username, password));
// Return correct authentication
return callback(null, true);
} else if (count === 0) {
if (errorObject == null)
errorObject = new MongoError(f('failed to authenticate using scram'));
return callback(errorObject, false);
}
const payload = Buffer.isBuffer(r.payload) ? new Binary(r.payload) : r.payload;
const dict = parsePayload(payload.value());
const iterations = parseInt(dict.i, 10);
const salt = dict.s;
const rnonce = dict.r;
return;
// Set up start of proof
const withoutProof = `c=biws,r=${rnonce}`;
const saltedPassword = HI(
processedPassword,
Buffer.from(salt, 'base64'),
iterations,
cryptoMethod
);
if (iterations && iterations < 4096) {
const error = new MongoError(`Server returned an invalid iteration count ${iterations}`);
return callback(error, false);
}
const clientKey = HMAC(cryptoMethod, saltedPassword, 'Client Key');
const storedKey = H(cryptoMethod, clientKey);
const authMessage = [firstBare, payload.value().toString('base64'), withoutProof].join(',');
const clientSignature = HMAC(cryptoMethod, storedKey, authMessage);
const clientProof = `p=${xor(clientKey, clientSignature)}`;
const clientFinal = [withoutProof, clientProof].join(',');
const saslContinueCmd = {
saslContinue: 1,
conversationId: r.conversationId,
payload: new Binary(Buffer.from(clientFinal))
};
sendAuthCommand(connection, `${db}.$cmd`, saslContinueCmd, (err, r) => {
if (!r || r.done !== false) {
return callback(err, r);
}
// Get the dictionary
var dict = parsePayload(r.result.payload.value());
// Unpack dictionary
var iterations = parseInt(dict.i, 10);
var salt = dict.s;
var rnonce = dict.r;
// Set up start of proof
var withoutProof = f('c=biws,r=%s', rnonce);
var saltedPassword = HI(
processedPassword,
Buffer.from(salt, 'base64'),
iterations,
cryptoMethod
);
if (iterations && iterations < 4096) {
const error = new MongoError(`Server returned an invalid iteration count ${iterations}`);
return callback(error, false);
}
// Create the client key
const clientKey = HMAC(cryptoMethod, saltedPassword, 'Client Key');
// Create the stored key
const storedKey = H(cryptoMethod, clientKey);
// Create the authentication message
const authMessage = [
firstBare,
r.result.payload.value().toString('base64'),
withoutProof
].join(',');
// Create client signature
const clientSignature = HMAC(cryptoMethod, storedKey, authMessage);
// Create client proof
const clientProof = f('p=%s', xor(clientKey, clientSignature));
// Create client final
const clientFinal = [withoutProof, clientProof].join(',');
// Create continue message
const cmd = {
const retrySaslContinueCmd = {
saslContinue: 1,
conversationId: r.result.conversationId,
payload: new Binary(Buffer.from(clientFinal))
conversationId: r.conversationId,
payload: Buffer.alloc(0)
};
//
// Execute sasl continue
// Write the commmand on the connection
server(
connection,
new Query(self.bson, f('%s.$cmd', db), cmd, {
numberToSkip: 0,
numberToReturn: 1
}),
function(err, r) {
if (r && r.result.done === false) {
var cmd = {
saslContinue: 1,
conversationId: r.result.conversationId,
payload: Buffer.alloc(0)
};
// Write the commmand on the connection
server(
connection,
new Query(self.bson, f('%s.$cmd', db), cmd, {
numberToSkip: 0,
numberToReturn: 1
}),
function(err, r) {
handleEnd(err, r);
}
);
} else {
handleEnd(err, r);
}
}
);
}
);
};
var _execute = function(_connection) {
process.nextTick(function() {
executeScram(_connection);
sendAuthCommand(connection, `${db}.$cmd`, retrySaslContinueCmd, callback);
});
});
};
// For each connection we need to authenticate
while (connections.length > 0) {
_execute(connections.shift());
}
};
// Add to store only if it does not exist
var addAuthSession = function(authStore, session) {
var found = false;
/**
* Implementation of authentication for a single connection
* @override
*/
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
// Create a random nonce
crypto.randomBytes(24, (err, buff) => {
if (err) {
return callback(err, null);
}
for (var i = 0; i < authStore.length; i++) {
if (authStore[i].equal(session)) {
found = true;
break;
return this._executeScram(
sendAuthCommand,
connection,
credentials,
buff.toString('base64'),
callback
);
});
}
/**
* Authenticate
* @override
* @method
*/
auth(sendAuthCommand, connections, credentials, callback) {
this._checkSaslprep();
super.auth(sendAuthCommand, connections, credentials, callback);
}
_checkSaslprep() {
const cryptoMethod = this.cryptoMethod;
if (cryptoMethod === 'sha256') {
if (!saslprep) {
console.warn('Warning: no saslprep library specified. Passwords will not be sanitized');
}
}
}
if (!found) authStore.push(session);
};
}
/**
* Remove authStore credentials
* @method
* @param {string} db Name of database we are removing authStore details about
* @return {object}
* Creates a new ScramSHA1 authentication mechanism
* @class
* @extends ScramSHA
*/
ScramSHA.prototype.logout = function(dbName) {
this.authStore = this.authStore.filter(function(x) {
return x.db !== dbName;
});
};
/**
* Re authenticate pool
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
ScramSHA.prototype.reauthenticate = function(server, connections, callback) {
var authStore = this.authStore.slice(0);
var count = authStore.length;
// No connections
if (count === 0) return callback(null, null);
// Iterate over all the auth details stored
for (var i = 0; i < authStore.length; i++) {
this.auth(
server,
connections,
authStore[i].db,
authStore[i].username,
authStore[i].password,
function(err) {
count = count - 1;
// Done re-authenticating
if (count === 0) {
callback(err, null);
}
}
);
}
};
class ScramSHA1 extends ScramSHA {
constructor(bson) {
super(bson, 'sha1');
}
}
/**
* Creates a new ScramSHA256 authentication mechanism
* @class
* @extends ScramSHA
*/
class ScramSHA256 extends ScramSHA {
constructor(bson) {
super(bson, 'sha256');
+47 -178
View File
@@ -1,117 +1,59 @@
'use strict';
const f = require('util').format;
const Query = require('../connection/commands').Query;
const MongoError = require('../error').MongoError;
const AuthProvider = require('./auth_provider').AuthProvider;
const retrieveKerberos = require('../utils').retrieveKerberos;
var AuthSession = function(db, username, password, options) {
this.db = db;
this.username = username;
this.password = password;
this.options = options;
};
AuthSession.prototype.equal = function(session) {
return (
session.db === this.db &&
session.username === this.username &&
session.password === this.password
);
};
let kerberos;
/**
* Creates a new SSPI authentication mechanism
* @class
* @return {SSPI} A cursor instance
* @extends AuthProvider
*/
var SSPI = function(bson) {
this.bson = bson;
this.authStore = [];
};
class SSPI extends AuthProvider {
/**
* Implementation of authentication for a single connection
* @override
*/
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
// TODO: Destructure this
const username = credentials.username;
const password = credentials.password;
const mechanismProperties = credentials.mechanismProperties;
const gssapiServiceName =
mechanismProperties['gssapiservicename'] ||
mechanismProperties['gssapiServiceName'] ||
'mongodb';
/**
* Authenticate
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {string} db Name of the database
* @param {string} username Username
* @param {string} password Password
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
SSPI.prototype.auth = function(server, connections, db, username, password, options, callback) {
var self = this;
let kerberos;
try {
kerberos = retrieveKerberos();
} catch (e) {
return callback(e, null);
SSIPAuthenticate(
this,
kerberos.processes.MongoAuthProcess,
username,
password,
gssapiServiceName,
sendAuthCommand,
connection,
mechanismProperties,
callback
);
}
var gssapiServiceName = options['gssapiServiceName'] || 'mongodb';
// Total connections
var count = connections.length;
if (count === 0) return callback(null, null);
/**
* Authenticate
* @override
* @method
*/
auth(sendAuthCommand, connections, credentials, callback) {
if (kerberos == null) {
try {
kerberos = retrieveKerberos();
} catch (e) {
return callback(e, null);
}
}
// Valid connections
var numberOfValidConnections = 0;
var errorObject = null;
// For each connection we need to authenticate
while (connections.length > 0) {
// Execute MongoCR
var execute = function(connection) {
// Start Auth process for a connection
SSIPAuthenticate(
self,
kerberos.processes.MongoAuthProcess,
username,
password,
gssapiServiceName,
server,
connection,
options,
function(err, r) {
// Adjust count
count = count - 1;
// If we have an error
if (err) {
errorObject = err;
} else if (r && typeof r === 'object' && r.result['$err']) {
errorObject = r.result;
} else if (r && typeof r === 'object' && r.result['errmsg']) {
errorObject = r.result;
} else {
numberOfValidConnections = numberOfValidConnections + 1;
}
// We have authenticated all connections
if (count === 0 && numberOfValidConnections > 0) {
// Store the auth details
addAuthSession(self.authStore, new AuthSession(db, username, password, options));
// Return correct authentication
callback(null, true);
} else if (count === 0) {
if (errorObject == null)
errorObject = new MongoError(f('failed to authenticate using mongocr'));
callback(errorObject, false);
}
}
);
};
var _execute = function(_connection) {
process.nextTick(function() {
execute(_connection);
});
};
_execute(connections.shift());
super.auth(sendAuthCommand, connections, credentials, callback);
}
};
}
function SSIPAuthenticate(
self,
@@ -119,7 +61,7 @@ function SSIPAuthenticate(
username,
password,
gssapiServiceName,
server,
sendAuthCommand,
connection,
options,
callback
@@ -132,12 +74,7 @@ function SSIPAuthenticate(
);
function authCommand(command, authCb) {
const query = new Query(self.bson, '$external.$cmd', command, {
numberToSkip: 0,
numberToReturn: 1
});
server(connection, query, authCb);
sendAuthCommand(connection, '$external.$cmd', command, authCb);
}
authProcess.init(username, password, err => {
@@ -153,9 +90,8 @@ function SSIPAuthenticate(
autoAuthorize: 1
};
authCommand(command, (err, result) => {
authCommand(command, (err, doc) => {
if (err) return callback(err, false);
const doc = result.result;
authProcess.transition(doc.payload, (err, payload) => {
if (err) return callback(err, false);
@@ -165,9 +101,8 @@ function SSIPAuthenticate(
payload
};
authCommand(command, (err, result) => {
authCommand(command, (err, doc) => {
if (err) return callback(err, false);
const doc = result.result;
authProcess.transition(doc.payload, (err, payload) => {
if (err) return callback(err, false);
@@ -193,70 +128,4 @@ function SSIPAuthenticate(
});
}
// Add to store only if it does not exist
var addAuthSession = function(authStore, session) {
var found = false;
for (var i = 0; i < authStore.length; i++) {
if (authStore[i].equal(session)) {
found = true;
break;
}
}
if (!found) authStore.push(session);
};
/**
* Remove authStore credentials
* @method
* @param {string} db Name of database we are removing authStore details about
* @return {object}
*/
SSPI.prototype.logout = function(dbName) {
this.authStore = this.authStore.filter(function(x) {
return x.db !== dbName;
});
};
/**
* Re authenticate pool
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
SSPI.prototype.reauthenticate = function(server, connections, callback) {
var authStore = this.authStore.slice(0);
var count = authStore.length;
if (count === 0) return callback(null, null);
// Iterate over all the auth details stored
for (var i = 0; i < authStore.length; i++) {
this.auth(
server,
connections,
authStore[i].db,
authStore[i].username,
authStore[i].password,
authStore[i].options,
function(err) {
count = count - 1;
// Done re-authenticating
if (count === 0) {
callback(err, null);
}
}
);
}
};
/**
* This is a result from a authentication strategy
*
* @callback authResultCallback
* @param {error} error An error object. Set to null if no error present
* @param {boolean} result The result of the authentication process
*/
module.exports = SSPI;
+15 -168
View File
@@ -1,179 +1,26 @@
'use strict';
var f = require('util').format,
Query = require('../connection/commands').Query,
MongoError = require('../error').MongoError;
var AuthSession = function(db, username, password) {
this.db = db;
this.username = username;
this.password = password;
};
AuthSession.prototype.equal = function(session) {
return (
session.db === this.db &&
session.username === this.username &&
session.password === this.password
);
};
const AuthProvider = require('./auth_provider').AuthProvider;
/**
* Creates a new X509 authentication mechanism
* @class
* @return {X509} A cursor instance
* @extends AuthProvider
*/
var X509 = function(bson) {
this.bson = bson;
this.authStore = [];
};
/**
* Authenticate
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {string} db Name of the database
* @param {string} username Username
* @param {string} password Password
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
X509.prototype.auth = function(server, connections, db, username, password, callback) {
var self = this;
// Total connections
var count = connections.length;
if (count === 0) return callback(null, null);
// Valid connections
var numberOfValidConnections = 0;
var errorObject = null;
// For each connection we need to authenticate
while (connections.length > 0) {
// Execute MongoCR
var execute = function(connection) {
// Let's start the sasl process
var command = {
authenticate: 1,
mechanism: 'MONGODB-X509'
};
// Add username if specified
if (username) {
command.user = username;
}
// Let's start the process
server(
connection,
new Query(self.bson, '$external.$cmd', command, {
numberToSkip: 0,
numberToReturn: 1
}),
function(err, r) {
// Adjust count
count = count - 1;
// If we have an error
if (err) {
errorObject = err;
} else if (r.result['$err']) {
errorObject = r.result;
} else if (r.result['errmsg']) {
errorObject = r.result;
} else {
numberOfValidConnections = numberOfValidConnections + 1;
}
// We have authenticated all connections
if (count === 0 && numberOfValidConnections > 0) {
// Store the auth details
addAuthSession(self.authStore, new AuthSession(db, username, password));
// Return correct authentication
callback(null, true);
} else if (count === 0) {
if (errorObject == null)
errorObject = new MongoError(f('failed to authenticate using mongocr'));
callback(errorObject, false);
}
}
);
};
var _execute = function(_connection) {
process.nextTick(function() {
execute(_connection);
});
};
_execute(connections.shift());
}
};
// Add to store only if it does not exist
var addAuthSession = function(authStore, session) {
var found = false;
for (var i = 0; i < authStore.length; i++) {
if (authStore[i].equal(session)) {
found = true;
break;
class X509 extends AuthProvider {
/**
* Implementation of authentication for a single connection
* @override
*/
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
const username = credentials.username;
const command = { authenticate: 1, mechanism: 'MONGODB-X509' };
if (username) {
command.user = username;
}
sendAuthCommand(connection, '$external.$cmd', command, callback);
}
if (!found) authStore.push(session);
};
/**
* Remove authStore credentials
* @method
* @param {string} db Name of database we are removing authStore details about
* @return {object}
*/
X509.prototype.logout = function(dbName) {
this.authStore = this.authStore.filter(function(x) {
return x.db !== dbName;
});
};
/**
* Re authenticate pool
* @method
* @param {{Server}|{ReplSet}|{Mongos}} server Topology the authentication method is being called on
* @param {[]Connections} connections Connections to authenticate using this authenticator
* @param {authResultCallback} callback The callback to return the result from the authentication
* @return {object}
*/
X509.prototype.reauthenticate = function(server, connections, callback) {
var authStore = this.authStore.slice(0);
var count = authStore.length;
if (count === 0) return callback(null, null);
// Iterate over all the auth details stored
for (var i = 0; i < authStore.length; i++) {
this.auth(
server,
connections,
authStore[i].db,
authStore[i].username,
authStore[i].password,
function(err) {
count = count - 1;
// Done re-authenticating
if (count === 0) {
callback(err, null);
}
}
);
}
};
/**
* This is a result from a authentication strategy
*
* @callback authResultCallback
* @param {error} error An error object. Set to null if no error present
* @param {boolean} result The result of the authentication process
*/
}
module.exports = X509;