test
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
mol
2024-07-06 22:23:31 +08:00
parent 08173d8497
commit 263cb5ef03
1663 changed files with 526884 additions and 0 deletions

View File

@ -0,0 +1,151 @@
'use strict';
const dns = require('dns');
const kerberos = require('../kerberos');
class MongoAuthProcess {
constructor(host, port, serviceName, options) {
options = options || {};
this.host = host;
this.port = port;
// Set up service name
this.serviceName = serviceName || options.gssapiServiceName || 'mongodb';
// Options
this.canonicalizeHostName =
typeof options.gssapiCanonicalizeHostName === 'boolean'
? options.gssapiCanonicalizeHostName
: false;
// Set up first transition
this._transition = firstTransition(this);
// Number of retries
this.retries = 10;
}
init(username, password, callback) {
const self = this;
this.username = username;
this.password = password;
// Canonicialize host name if needed
function performGssapiCanonicalizeHostName(canonicalizeHostName, host, callback) {
if (!canonicalizeHostName) return callback();
// Attempt to resolve the host name
dns.resolveCname(host, (err, r) => {
if (err) return callback(err);
// Get the first resolve host id
if (Array.isArray(r) && r.length > 0) {
self.host = r[0];
}
callback();
});
}
// Canonicialize host name if needed
performGssapiCanonicalizeHostName(this.canonicalizeHostName, this.host, err => {
if (err) return callback(err);
const initOptions = {};
if (password != null) {
Object.assign(initOptions, { user: username, password });
}
const service =
process.platform === 'win32'
? `${this.serviceName}/${this.host}`
: `${this.serviceName}@${this.host}`;
kerberos.initializeClient(service, initOptions, (err, client) => {
if (err) return callback(err, null);
self.client = client;
callback(null, client);
});
});
}
transition(payload, callback) {
if (this._transition == null) {
return callback(new Error('Transition finished'));
}
this._transition(payload, callback);
}
}
function firstTransition(auth) {
return (payload, callback) => {
auth.client.step('', (err, response) => {
if (err) return callback(err);
// Set up the next step
auth._transition = secondTransition(auth);
// Return the payload
callback(null, response);
});
};
}
function secondTransition(auth) {
return (payload, callback) => {
auth.client.step(payload, (err, response) => {
if (err && auth.retries === 0) return callback(err);
// Attempt to re-establish a context
if (err) {
// Adjust the number of retries
auth.retries = auth.retries - 1;
// Call same step again
return auth.transition(payload, callback);
}
// Set up the next step
auth._transition = thirdTransition(auth);
// Return the payload
callback(null, response || '');
});
};
}
function thirdTransition(auth) {
return (payload, callback) => {
// GSS Client Unwrap
auth.client.unwrap(payload, (err, response) => {
if (err) return callback(err, false);
// Wrap the response
auth.client.wrap(response, { user: auth.username }, (err, wrapped) => {
if (err) return callback(err, false);
// Set up the next step
auth._transition = fourthTransition(auth);
// Return the payload
callback(null, wrapped);
});
});
};
}
function fourthTransition(auth) {
return (payload, callback) => {
// Set the transition to null
auth._transition = null;
// Callback with valid authentication
callback(null, true);
};
}
// Set the process
module.exports = {
MongoAuthProcess
};

View File

@ -0,0 +1,16 @@
'use strict';
const kerberos = require('./kerberos');
// Get the Kerberos library
module.exports = kerberos;
// Support legacy versions of the mongodb driver which expect this export
module.exports.Kerberos = kerberos;
module.exports.version = require('../package.json').version;
// Set up the auth processes
module.exports.processes = {
MongoAuthProcess: require('./auth_processes/mongodb').MongoAuthProcess
};

View File

@ -0,0 +1,203 @@
'use strict';
const kerberos = require('bindings')('kerberos');
const KerberosClient = kerberos.KerberosClient;
const KerberosServer = kerberos.KerberosServer;
const defineOperation = require('./util').defineOperation;
// GSS Flags
const GSS_C_DELEG_FLAG = 1;
const GSS_C_MUTUAL_FLAG = 2;
const GSS_C_REPLAY_FLAG = 4;
const GSS_C_SEQUENCE_FLAG = 8;
const GSS_C_CONF_FLAG = 16;
const GSS_C_INTEG_FLAG = 32;
const GSS_C_ANON_FLAG = 64;
const GSS_C_PROT_READY_FLAG = 128;
const GSS_C_TRANS_FLAG = 256;
// GSS_OID
const GSS_C_NO_OID = 0;
const GSS_MECH_OID_KRB5 = 9;
const GSS_MECH_OID_SPNEGO = 6;
/**
* @class KerberosClient
*
* @property {string} username The username used for authentication
* @property {string} response The last response received during authentication steps
* @property {string} responseConf Indicates whether confidentiality was applied or not (GSSAPI only)
* @property {boolean} contextComplete Indicates that authentication has successfully completed or not
*/
/**
* Processes a single kerberos client-side step using the supplied server challenge.
*
* @kind function
* @memberof KerberosClient
* @param {string} challenge A string containing the base64-encoded server data (which may be empty for the first step)
* @param {function} [callback]
* @return {Promise} returns Promise if no callback passed
*/
KerberosClient.prototype.step = defineOperation(KerberosClient.prototype.step, [
{ name: 'challenge', type: 'string' },
{ name: 'callback', type: 'function', required: false }
]);
/**
* Perform the client side kerberos wrap step.
*
* @kind function
* @memberof KerberosClient
* @param {string} challenge The response returned after calling `unwrap`
* @param {object} [options] Optional settings
* @param {string} [options.user] The user to authorize
* @param {function} [callback]
* @return {Promise} returns Promise if no callback passed
*/
KerberosClient.prototype.wrap = defineOperation(KerberosClient.prototype.wrap, [
{ name: 'challenge', type: 'string' },
{ name: 'options', type: 'object' },
{ name: 'callback', type: 'function', required: false }
]);
/**
* Perform the client side kerberos unwrap step
*
* @kind function
* @memberof KerberosClient
* @param {string} challenge A string containing the base64-encoded server data
* @param {function} [callback]
* @return {Promise} returns Promise if no callback passed
*/
KerberosClient.prototype.unwrap = defineOperation(KerberosClient.prototype.unwrap, [
{ name: 'challenge', type: 'string' },
{ name: 'callback', type: 'function', required: false }
]);
/**
* @class KerberosServer
*
* @property {string} username The username used for authentication
* @property {string} response The last response received during authentication steps
* @property {string} targetName The target used for authentication
* @property {boolean} contextComplete Indicates that authentication has successfully completed or not
*/
/**
* Processes a single kerberos server-side step using the supplied client data.
*
* @kind function
* @memberof KerberosServer
* @param {string} challenge A string containing the base64-encoded client data
* @param {function} [callback]
* @return {Promise} returns Promise if no callback passed
*/
KerberosServer.prototype.step = defineOperation(KerberosServer.prototype.step, [
{ name: 'challenge', type: 'string' },
{ name: 'callback', type: 'function', required: false }
]);
/**
* This function provides a simple way to verify that a user name and password
* match those normally used for Kerberos authentication.
* It does this by checking that the supplied user name and password can be
* used to get a ticket for the supplied service.
* If the user name does not contain a realm, then the default realm supplied
* is used.
*
* For this to work properly the Kerberos must be configured properly on this
* machine.
* That will likely mean ensuring that the edu.mit.Kerberos preference file
* has the correct realms and KDCs listed.
*
* IMPORTANT: This method is vulnerable to KDC spoofing attacks and it should
* only be used for testing. Do not use this in any production system - your
* security could be compromised if you do.
*
* @kind function
* @param {string} username The Kerberos user name. If no realm is supplied, then the `defaultRealm` will be used.
* @param {string} password The password for the user.
* @param {string} service The Kerberos service to check access for.
* @param {string} [defaultRealm] The default realm to use if one is not supplied in the user argument.
* @param {function} [callback]
* @return {Promise} returns Promise if no callback passed
*/
const checkPassword = defineOperation(kerberos.checkPassword, [
{ name: 'username', type: 'string' },
{ name: 'password', type: 'string' },
{ name: 'service', type: 'string' },
{ name: 'defaultRealm', type: 'string', required: false },
{ name: 'callback', type: 'function', required: false }
]);
/**
* This function returns the service principal for the server given a service type and hostname.
*
* Details are looked up via the `/etc/keytab` file.
*
* @kind function
* @param {string} service The Kerberos service type for the server.
* @param {string} hostname The hostname of the server.
* @param {function} [callback]
* @return {Promise} returns Promise if no callback passed
*/
const principalDetails = defineOperation(kerberos.principalDetails, [
{ name: 'service', type: 'string' },
{ name: 'hostname', type: 'string' },
{ name: 'callback', type: 'function', required: false }
]);
/**
* Initializes a context for client-side authentication with the given service principal.
*
* @kind function
* @param {string} service A string containing the service principal in the form 'type@fqdn' (e.g. 'imap@mail.apple.com').
* @param {object} [options] Optional settings
* @param {string} [options.principal] Optional string containing the client principal in the form 'user@realm' (e.g. 'jdoe@example.com').
* @param {number} [options.gssFlags] Optional integer used to set GSS flags. (e.g. GSS_C_DELEG_FLAG|GSS_C_MUTUAL_FLAG|GSS_C_SEQUENCE_FLAG will allow for forwarding credentials to the remote host)
* @param {number} [options.mechOID] Optional GSS mech OID. Defaults to None (GSS_C_NO_OID). Other possible values are `GSS_MECH_OID_KRB5`, `GSS_MECH_OID_SPNEGO`.
* @param {function} [callback]
* @return {Promise} returns Promise if no callback passed
*/
const initializeClient = defineOperation(kerberos.initializeClient, [
{ name: 'service', type: 'string' },
{ name: 'options', type: 'object', default: { mechOID: GSS_C_NO_OID } },
{ name: 'callback', type: 'function', required: false }
]);
/**
* Initializes a context for server-side authentication with the given service principal.
*
* @kind function
* @param {string} service A string containing the service principal in the form 'type@fqdn' (e.g. 'imap@mail.apple.com').
* @param {function} [callback]
* @return {Promise} returns Promise if no callback passed
*/
const initializeServer = defineOperation(kerberos.initializeServer, [
{ name: 'service', type: 'string' },
{ name: 'callback', type: 'function', required: false }
]);
module.exports = {
initializeClient,
initializeServer,
principalDetails,
checkPassword,
// gss flags
GSS_C_DELEG_FLAG,
GSS_C_MUTUAL_FLAG,
GSS_C_REPLAY_FLAG,
GSS_C_SEQUENCE_FLAG,
GSS_C_CONF_FLAG,
GSS_C_INTEG_FLAG,
GSS_C_ANON_FLAG,
GSS_C_PROT_READY_FLAG,
GSS_C_TRANS_FLAG,
GSS_C_NO_OID,
// mechanism OIDs
GSS_MECH_OID_KRB5,
GSS_MECH_OID_SPNEGO
};

View File

@ -0,0 +1,84 @@
'use strict';
function validateParameter(parameter, specs, specIndex) {
const spec = specs[specIndex];
if (parameter == null && spec.required === false) {
return;
}
if (parameter == null) {
throw new TypeError(`Required parameter \`${spec.name}\` missing`);
}
const paramType = typeof parameter;
if (spec.type && paramType !== spec.type) {
if (spec.required === false) {
if (specs.slice(specIndex).some(def => def.type === paramType)) {
return false;
}
}
throw new TypeError(
`Invalid type for parameter \`${spec.name}\`, expected \`${
spec.type
}\` but found \`${typeof parameter}\``
);
}
return true;
}
function hasOwnProperty(object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
}
/**
* Monkey-patches an existing method to support parameter validation, as well
* as adding support for returning Promises if callbacks are not provided.
*
* @private
* @param {function} fn the function to override
* @param {Array<Object>} paramDefs the definitions of each parameter to the function
*/
function defineOperation(fn, paramDefs) {
return function () {
const args = Array.prototype.slice.call(arguments);
const params = [];
for (let i = 0, argIdx = 0; i < paramDefs.length; ++i, ++argIdx) {
const def = paramDefs[i];
let arg = args[argIdx];
if (hasOwnProperty(def, 'default') && arg == null) arg = def.default;
if (def.type === 'object' && def.default != null) {
arg = Object.assign({}, def.default, arg);
}
// special case to allow `options` to be optional
if (def.name === 'options' && (typeof arg === 'function' || arg == null)) {
arg = {};
}
if (validateParameter(arg, paramDefs, i)) {
params.push(arg);
} else {
argIdx--;
}
}
const callback = arguments[arguments.length - 1];
if (typeof callback !== 'function') {
return new Promise((resolve, reject) => {
params.push((err, response) => {
if (err) return reject(err);
resolve(response);
});
fn.apply(this, params);
});
}
fn.apply(this, params);
};
}
module.exports = { defineOperation, validateParameter };