-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(kerberos): move MongoAuthProcess into driver (#2535)
- Loading branch information
Showing
1 changed file
with
122 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,91 +1,151 @@ | ||
'use strict'; | ||
const dns = require('dns'); | ||
|
||
const AuthProvider = require('./auth_provider').AuthProvider; | ||
const retrieveKerberos = require('../utils').retrieveKerberos; | ||
const MongoError = require('../error').MongoError; | ||
|
||
const kGssapiClient = Symbol('GSSAPI_CLIENT'); | ||
let kerberos; | ||
|
||
class GSSAPI extends AuthProvider { | ||
auth(authContext, callback) { | ||
const connection = authContext.connection; | ||
prepare(handshakeDoc, authContext, callback) { | ||
const host = authContext.options.host; | ||
const port = authContext.options.port; | ||
const credentials = authContext.credentials; | ||
|
||
if (!host || !port || !credentials) { | ||
return callback( | ||
new MongoError( | ||
`Connection must specify: ${host ? 'host' : ''}, ${port ? 'port' : ''}, ${ | ||
credentials ? 'host' : 'credentials' | ||
}.` | ||
) | ||
); | ||
} | ||
if (kerberos == null) { | ||
try { | ||
kerberos = retrieveKerberos(); | ||
} catch (e) { | ||
return callback(e, null); | ||
return callback(e); | ||
} | ||
} | ||
|
||
// TODO: Destructure this | ||
const username = credentials.username; | ||
const password = credentials.password; | ||
const mechanismProperties = credentials.mechanismProperties; | ||
const gssapiServiceName = | ||
const serviceName = | ||
mechanismProperties['gssapiservicename'] || | ||
mechanismProperties['gssapiServiceName'] || | ||
'mongodb'; | ||
|
||
const MongoAuthProcess = kerberos.processes.MongoAuthProcess; | ||
const authProcess = new MongoAuthProcess( | ||
connection.host, | ||
connection.port, | ||
gssapiServiceName, | ||
mechanismProperties | ||
); | ||
|
||
authProcess.init(username, password, err => { | ||
if (err) return callback(err, false); | ||
|
||
authProcess.transition('', (err, payload) => { | ||
if (err) return callback(err, false); | ||
|
||
const command = { | ||
saslStart: 1, | ||
mechanism: 'GSSAPI', | ||
payload, | ||
autoAuthorize: 1 | ||
}; | ||
|
||
connection.command('$external.$cmd', command, (err, result) => { | ||
if (err) return callback(err, false); | ||
|
||
const doc = result.result; | ||
authProcess.transition(doc.payload, (err, payload) => { | ||
if (err) return callback(err, false); | ||
const command = { | ||
saslContinue: 1, | ||
conversationId: doc.conversationId, | ||
payload | ||
}; | ||
|
||
connection.command('$external.$cmd', command, (err, result) => { | ||
if (err) return callback(err, false); | ||
|
||
const doc = result.result; | ||
authProcess.transition(doc.payload, (err, payload) => { | ||
if (err) return callback(err, false); | ||
const command = { | ||
performGssapiCanonicalizeHostName(host, mechanismProperties, (err, host) => { | ||
if (err) return callback(err); | ||
const initOptions = {}; | ||
if (password != null) { | ||
Object.assign(initOptions, { user: username, password: password }); | ||
} | ||
kerberos.initializeClient( | ||
`${serviceName}${process.platform === 'win32' ? '/' : '@'}${host}`, | ||
initOptions, | ||
(err, client) => { | ||
if (err) return callback(new MongoError(err)); | ||
if (client == null) return callback(); | ||
this[kGssapiClient] = client; | ||
callback(undefined, handshakeDoc); | ||
} | ||
); | ||
}); | ||
} | ||
auth(authContext, callback) { | ||
const connection = authContext.connection; | ||
const credentials = authContext.credentials; | ||
if (credentials == null) return callback(new MongoError('credentials required')); | ||
const username = credentials.username; | ||
const client = this[kGssapiClient]; | ||
if (client == null) return callback(new MongoError('gssapi client missing')); | ||
function externalCommand(command, cb) { | ||
return connection.command('$external.$cmd', command, cb); | ||
} | ||
client.step('', (err, payload) => { | ||
if (err) return callback(err); | ||
externalCommand(saslStart(payload), (err, response) => { | ||
const result = response.result; | ||
if (err) return callback(err); | ||
negotiate(client, 10, result.payload, (err, payload) => { | ||
if (err) return callback(err); | ||
externalCommand(saslContinue(payload, result.conversationId), (err, response) => { | ||
const result = response.result; | ||
if (err) return callback(err); | ||
finalize(client, username, result.payload, (err, payload) => { | ||
if (err) return callback(err); | ||
externalCommand( | ||
{ | ||
saslContinue: 1, | ||
conversationId: doc.conversationId, | ||
conversationId: result.conversationId, | ||
payload | ||
}; | ||
|
||
connection.command('$external.$cmd', command, (err, result) => { | ||
if (err) return callback(err, false); | ||
|
||
const response = result.result; | ||
authProcess.transition(null, err => { | ||
if (err) return callback(err, null); | ||
callback(null, response); | ||
}); | ||
}); | ||
}); | ||
}, | ||
(err, result) => { | ||
if (err) return callback(err); | ||
callback(undefined, result); | ||
} | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
module.exports = GSSAPI; | ||
|
||
function saslStart(payload) { | ||
return { | ||
saslStart: 1, | ||
mechanism: 'GSSAPI', | ||
payload, | ||
autoAuthorize: 1 | ||
}; | ||
} | ||
function saslContinue(payload, conversationId) { | ||
return { | ||
saslContinue: 1, | ||
conversationId, | ||
payload | ||
}; | ||
} | ||
function negotiate(client, retries, payload, callback) { | ||
client.step(payload, (err, response) => { | ||
// Retries exhausted, raise error | ||
if (err && retries === 0) return callback(err); | ||
// Adjust number of retries and call step again | ||
if (err) return negotiate(client, retries - 1, payload, callback); | ||
// Return the payload | ||
callback(undefined, response || ''); | ||
}); | ||
} | ||
function finalize(client, user, payload, callback) { | ||
// GSS Client Unwrap | ||
client.unwrap(payload, (err, response) => { | ||
if (err) return callback(err); | ||
// Wrap the response | ||
client.wrap(response || '', { user }, (err, wrapped) => { | ||
if (err) return callback(err); | ||
// Return the payload | ||
callback(undefined, wrapped); | ||
}); | ||
}); | ||
} | ||
function performGssapiCanonicalizeHostName(host, mechanismProperties, callback) { | ||
const canonicalizeHostName = | ||
typeof mechanismProperties.gssapiCanonicalizeHostName === 'boolean' | ||
? mechanismProperties.gssapiCanonicalizeHostName | ||
: false; | ||
if (!canonicalizeHostName) return callback(undefined, host); | ||
// 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) { | ||
return callback(undefined, r[0]); | ||
} | ||
callback(undefined, host); | ||
}); | ||
} |