Skip to content

Commit c510fde

Browse files
authored
refactor(kerberos): move MongoAuthProcess into driver (#2535)
NODE-2730
1 parent 0af3191 commit c510fde

File tree

1 file changed

+122
-62
lines changed

1 file changed

+122
-62
lines changed

lib/core/auth/gssapi.js

Lines changed: 122 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,151 @@
11
'use strict';
2+
const dns = require('dns');
3+
24
const AuthProvider = require('./auth_provider').AuthProvider;
35
const retrieveKerberos = require('../utils').retrieveKerberos;
6+
const MongoError = require('../error').MongoError;
7+
8+
const kGssapiClient = Symbol('GSSAPI_CLIENT');
49
let kerberos;
510

611
class GSSAPI extends AuthProvider {
7-
auth(authContext, callback) {
8-
const connection = authContext.connection;
12+
prepare(handshakeDoc, authContext, callback) {
13+
const host = authContext.options.host;
14+
const port = authContext.options.port;
915
const credentials = authContext.credentials;
10-
16+
if (!host || !port || !credentials) {
17+
return callback(
18+
new MongoError(
19+
`Connection must specify: ${host ? 'host' : ''}, ${port ? 'port' : ''}, ${
20+
credentials ? 'host' : 'credentials'
21+
}.`
22+
)
23+
);
24+
}
1125
if (kerberos == null) {
1226
try {
1327
kerberos = retrieveKerberos();
1428
} catch (e) {
15-
return callback(e, null);
29+
return callback(e);
1630
}
1731
}
18-
19-
// TODO: Destructure this
2032
const username = credentials.username;
2133
const password = credentials.password;
2234
const mechanismProperties = credentials.mechanismProperties;
23-
const gssapiServiceName =
35+
const serviceName =
2436
mechanismProperties['gssapiservicename'] ||
2537
mechanismProperties['gssapiServiceName'] ||
2638
'mongodb';
27-
28-
const MongoAuthProcess = kerberos.processes.MongoAuthProcess;
29-
const authProcess = new MongoAuthProcess(
30-
connection.host,
31-
connection.port,
32-
gssapiServiceName,
33-
mechanismProperties
34-
);
35-
36-
authProcess.init(username, password, err => {
37-
if (err) return callback(err, false);
38-
39-
authProcess.transition('', (err, payload) => {
40-
if (err) return callback(err, false);
41-
42-
const command = {
43-
saslStart: 1,
44-
mechanism: 'GSSAPI',
45-
payload,
46-
autoAuthorize: 1
47-
};
48-
49-
connection.command('$external.$cmd', command, (err, result) => {
50-
if (err) return callback(err, false);
51-
52-
const doc = result.result;
53-
authProcess.transition(doc.payload, (err, payload) => {
54-
if (err) return callback(err, false);
55-
const command = {
56-
saslContinue: 1,
57-
conversationId: doc.conversationId,
58-
payload
59-
};
60-
61-
connection.command('$external.$cmd', command, (err, result) => {
62-
if (err) return callback(err, false);
63-
64-
const doc = result.result;
65-
authProcess.transition(doc.payload, (err, payload) => {
66-
if (err) return callback(err, false);
67-
const command = {
39+
performGssapiCanonicalizeHostName(host, mechanismProperties, (err, host) => {
40+
if (err) return callback(err);
41+
const initOptions = {};
42+
if (password != null) {
43+
Object.assign(initOptions, { user: username, password: password });
44+
}
45+
kerberos.initializeClient(
46+
`${serviceName}${process.platform === 'win32' ? '/' : '@'}${host}`,
47+
initOptions,
48+
(err, client) => {
49+
if (err) return callback(new MongoError(err));
50+
if (client == null) return callback();
51+
this[kGssapiClient] = client;
52+
callback(undefined, handshakeDoc);
53+
}
54+
);
55+
});
56+
}
57+
auth(authContext, callback) {
58+
const connection = authContext.connection;
59+
const credentials = authContext.credentials;
60+
if (credentials == null) return callback(new MongoError('credentials required'));
61+
const username = credentials.username;
62+
const client = this[kGssapiClient];
63+
if (client == null) return callback(new MongoError('gssapi client missing'));
64+
function externalCommand(command, cb) {
65+
return connection.command('$external.$cmd', command, cb);
66+
}
67+
client.step('', (err, payload) => {
68+
if (err) return callback(err);
69+
externalCommand(saslStart(payload), (err, response) => {
70+
const result = response.result;
71+
if (err) return callback(err);
72+
negotiate(client, 10, result.payload, (err, payload) => {
73+
if (err) return callback(err);
74+
externalCommand(saslContinue(payload, result.conversationId), (err, response) => {
75+
const result = response.result;
76+
if (err) return callback(err);
77+
finalize(client, username, result.payload, (err, payload) => {
78+
if (err) return callback(err);
79+
externalCommand(
80+
{
6881
saslContinue: 1,
69-
conversationId: doc.conversationId,
82+
conversationId: result.conversationId,
7083
payload
71-
};
72-
73-
connection.command('$external.$cmd', command, (err, result) => {
74-
if (err) return callback(err, false);
75-
76-
const response = result.result;
77-
authProcess.transition(null, err => {
78-
if (err) return callback(err, null);
79-
callback(null, response);
80-
});
81-
});
82-
});
84+
},
85+
(err, result) => {
86+
if (err) return callback(err);
87+
callback(undefined, result);
88+
}
89+
);
8390
});
8491
});
8592
});
8693
});
8794
});
8895
}
8996
}
90-
9197
module.exports = GSSAPI;
98+
99+
function saslStart(payload) {
100+
return {
101+
saslStart: 1,
102+
mechanism: 'GSSAPI',
103+
payload,
104+
autoAuthorize: 1
105+
};
106+
}
107+
function saslContinue(payload, conversationId) {
108+
return {
109+
saslContinue: 1,
110+
conversationId,
111+
payload
112+
};
113+
}
114+
function negotiate(client, retries, payload, callback) {
115+
client.step(payload, (err, response) => {
116+
// Retries exhausted, raise error
117+
if (err && retries === 0) return callback(err);
118+
// Adjust number of retries and call step again
119+
if (err) return negotiate(client, retries - 1, payload, callback);
120+
// Return the payload
121+
callback(undefined, response || '');
122+
});
123+
}
124+
function finalize(client, user, payload, callback) {
125+
// GSS Client Unwrap
126+
client.unwrap(payload, (err, response) => {
127+
if (err) return callback(err);
128+
// Wrap the response
129+
client.wrap(response || '', { user }, (err, wrapped) => {
130+
if (err) return callback(err);
131+
// Return the payload
132+
callback(undefined, wrapped);
133+
});
134+
});
135+
}
136+
function performGssapiCanonicalizeHostName(host, mechanismProperties, callback) {
137+
const canonicalizeHostName =
138+
typeof mechanismProperties.gssapiCanonicalizeHostName === 'boolean'
139+
? mechanismProperties.gssapiCanonicalizeHostName
140+
: false;
141+
if (!canonicalizeHostName) return callback(undefined, host);
142+
// Attempt to resolve the host name
143+
dns.resolveCname(host, (err, r) => {
144+
if (err) return callback(err);
145+
// Get the first resolve host id
146+
if (Array.isArray(r) && r.length > 0) {
147+
return callback(undefined, r[0]);
148+
}
149+
callback(undefined, host);
150+
});
151+
}

0 commit comments

Comments
 (0)