Skip to content

Commit 3b77430

Browse files
committed
feat(promises): allow to access all API by promise or callback
1 parent 08857de commit 3b77430

File tree

2 files changed

+164
-69
lines changed

2 files changed

+164
-69
lines changed

lib/kerberos.js

+106-67
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
'use strict';
22

33
const kerberos = require('bindings')('kerberos');
4+
const KerberosClient = kerberos.KerberosClient;
5+
const KerberosServer = kerberos.KerberosServer;
46

57
// Result Codes
68
const AUTH_GSS_CONTINUE = 0;
@@ -36,6 +38,93 @@ function validateParameter(parameter, spec) {
3638
}
3739
}
3840

41+
/// allows api methods to be called with a callback or a promise
42+
function promisify(fn, paramDefs) {
43+
return function() {
44+
const args = Array.prototype.slice.call(arguments);
45+
const params = [];
46+
for (let i = 0; i < paramDefs.length; ++i) {
47+
const def = paramDefs[i];
48+
let arg = args[i];
49+
50+
if (def.default && arg == null) arg = def.default;
51+
if (def.type === 'object' && def.default != null) {
52+
arg = Object.assign({}, def.default, arg);
53+
}
54+
55+
// special case to allow `options` to be optional
56+
if (def.name === 'options' && (typeof arg === 'function' || arg == null)) {
57+
arg = {};
58+
}
59+
60+
validateParameter(arg, paramDefs[i]);
61+
params.push(arg);
62+
}
63+
64+
const callback = arguments[arguments.length - 1];
65+
if (typeof callback !== 'function') {
66+
return new Promise((resolve, reject) => {
67+
params.push((err, response) => {
68+
if (err) return reject(err);
69+
resolve(response);
70+
});
71+
72+
fn.apply(this, params);
73+
});
74+
}
75+
76+
params.push(callback);
77+
fn.apply(this, params);
78+
};
79+
}
80+
81+
/**
82+
* Processes a single GSSAPI client-side step using the supplied server data.
83+
*
84+
* @param {string} challenge A string containing the base64-encoded server data (which may be empty for the first step)
85+
* @param {function} callback Returns a result code, or an error if one was
86+
* @returns {string} response
87+
*/
88+
KerberosClient.prototype.step = promisify(KerberosClient.prototype.step, [
89+
{ name: 'challenge', type: 'string' }
90+
]);
91+
92+
/**
93+
* Perform the client side GSSAPI wrap step.
94+
*
95+
* @memberof KerberosClient
96+
* @param {string} challenge The result of the `authGSSClientResponse` after the `authGSSClientUnwrap`
97+
* @param {object} [options] Optional settings
98+
* @param {string} [options.user] The user to authorize
99+
* @param {function} callback
100+
*/
101+
KerberosClient.prototype.wrap = promisify(KerberosClient.prototype.wrap, [
102+
{ name: 'challenge', type: 'string' },
103+
{ name: 'options', type: 'object' }
104+
]);
105+
106+
/**
107+
* Perform the client side GSSAPI unwrap step
108+
*
109+
* @memberof KerberosClient
110+
* @param {string} challenge A string containing the base64-encoded server data
111+
* @param {function} [callback]
112+
*/
113+
KerberosClient.prototype.unwrap = promisify(KerberosClient.prototype.unwrap, [
114+
{ name: 'challenge', type: 'string' }
115+
]);
116+
117+
/**
118+
* Processes a single GSSAPI server-side step using the supplied client data.
119+
*
120+
* @memberof KerberosServer
121+
* @param {string} challenge A string containing the base64-encoded client data
122+
* @param {function} callback
123+
*/
124+
KerberosServer.prototype.step = promisify(KerberosServer.prototype.step, [
125+
{ name: 'challenge', type: 'string' }
126+
]);
127+
39128
/**
40129
* This function provides a simple way to verify that a user name and password
41130
* match those normally used for Kerberos authentication.
@@ -59,16 +148,12 @@ function validateParameter(parameter, spec) {
59148
* @param {string} [defaultRealm] The default realm to use if one is not supplied in the user argument.
60149
* @param {function} callback
61150
*/
62-
function checkPassword(username, password, service, defaultRealm, callback) {
63-
if (typeof defaultRealm === 'function') (callback = defaultRealm), (defaultRealm = null);
64-
validateParameter(username, { name: 'username', type: 'string' });
65-
validateParameter(password, { name: 'password', type: 'string' });
66-
validateParameter(service, { name: 'service', type: 'string' });
67-
validateParameter(defaultRealm, { name: 'defaultRealm', type: 'string' });
68-
validateParameter(callback, { name: 'callback', type: 'function' });
69-
70-
kerberos.checkPassword(username, password, service, defaultRealm, callback);
71-
}
151+
const checkPassword = promisify(kerberos.checkPassword, [
152+
{ name: 'username', type: 'string' },
153+
{ name: 'password', type: 'string' },
154+
{ name: 'service', type: 'string' },
155+
{ name: 'defaultRealm', type: 'string' }
156+
]);
72157

73158
/**
74159
* This function returns the service principal for the server given a service
@@ -80,13 +165,10 @@ function checkPassword(username, password, service, defaultRealm, callback) {
80165
* @param {string} hostname The hostname of the server.
81166
* @param {function} callback
82167
*/
83-
function principalDetails(service, hostname, callback) {
84-
validateParameter(service, { name: 'service', type: 'string' });
85-
validateParameter(hostname, { name: 'hostname', type: 'string' });
86-
validateParameter(callback, { name: 'callback', type: 'function' });
87-
88-
kerberos.principalDetails(service, hostname, callback);
89-
}
168+
const principalDetails = promisify(kerberos.principalDetails, [
169+
{ name: 'service', type: 'string' },
170+
{ name: 'hostname', type: 'string' }
171+
]);
90172

91173
/**
92174
* The callback format for inserts
@@ -108,16 +190,10 @@ function principalDetails(service, hostname, callback) {
108190
* @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.
109191
* @param {initializeClientCallback} callback The operation callback
110192
*/
111-
function initializeClient(service, options, callback) {
112-
if (typeof options === 'function') (callback = options), (options = {});
113-
options = Object.assign({}, { mechOID: GSS_C_NO_OID }, options);
114-
115-
validateParameter(service, { name: 'service', type: 'string' });
116-
validateParameter(options, { name: 'options', type: 'object' });
117-
validateParameter(callback, { name: 'callback', type: 'function' });
118-
119-
kerberos.initializeClient(service, options, callback);
120-
}
193+
const initializeClient = promisify(kerberos.initializeClient, [
194+
{ name: 'service', type: 'string' },
195+
{ name: 'options', type: 'object', default: { mechOID: GSS_C_NO_OID } }
196+
]);
121197

122198
/**
123199
* Initializes a context for GSSAPI server-side authentication with the given
@@ -128,46 +204,9 @@ function initializeClient(service, options, callback) {
128204
* @param {string} service A string containing the service principal in the form 'type@fqdn' (e.g. '[email protected]').
129205
* @param {initializeServerCallback} callback
130206
*/
131-
function initializeServer(service, callback) {
132-
validateParameter(service, { name: 'service', type: 'string' });
133-
validateParameter(callback, { name: 'callback', type: 'function' });
134-
135-
kerberos.initializeServer(service, callback);
136-
}
137-
138-
/**
139-
* Processes a single GSSAPI client-side step using the supplied server data.
140-
*
141-
* @memberof KerberosClient
142-
* @param {string} challenge A string containing the base64-encoded server data (which may be empty for the first step)
143-
* @param {function} callback Returns a result code, or an error if one was encountered
144-
*/
145-
146-
/**
147-
* Perform the client side GSSAPI unwrap step
148-
*
149-
* @memberof KerberosClient
150-
* @param {string} challenge A string containing the base64-encoded server data
151-
* @param {function} callback
152-
*/
153-
154-
/**
155-
* Perform the client side GSSAPI wrap step.
156-
*
157-
* @memberof KerberosClient
158-
* @param {string} challenge The result of the `authGSSClientResponse` after the `authGSSClientUnwrap`
159-
* @param {object} [options] Optional settings
160-
* @param {string} [options.user] The user to authorize
161-
* @param {function} callback
162-
*/
163-
164-
/**
165-
* Processes a single GSSAPI server-side step using the supplied client data.
166-
*
167-
* @memberof KerberosServer
168-
* @param {string} challenge A string containing the base64-encoded client data
169-
* @param {function} callback
170-
*/
207+
const initializeServer = promisify(kerberos.initializeServer, [
208+
{ name: 'service', type: 'string' }
209+
]);
171210

172211
module.exports = {
173212
initializeClient,

test/kerberos_win32_tests.js

+58-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ function authenticate(options, callback) {
2121
const start = options.start || false;
2222
const conversationId = options.conversationId;
2323

24+
let promise;
25+
if (callback == null || typeof callback !== 'function') {
26+
promise = new Promise((resolve, reject) => {
27+
callback = function(err, res) {
28+
if (err) return reject(err);
29+
resolve(res);
30+
};
31+
});
32+
}
33+
2434
if (start) {
2535
krbClient.step('', (err, payload) => {
2636
db.command({ saslStart: 1, mechanism: 'GSSAPI', payload }, (err, dbResponse) => {
@@ -36,7 +46,7 @@ function authenticate(options, callback) {
3646
});
3747
});
3848

39-
return;
49+
return promise;
4050
}
4151

4252
krbClient.step(challenge, (err, payload) => {
@@ -52,6 +62,8 @@ function authenticate(options, callback) {
5262
authenticate({ db, krbClient, conversationId, challenge: payload }, callback);
5363
});
5464
});
65+
66+
return promise;
5567
}
5668

5769
const test = {};
@@ -97,7 +109,7 @@ describe('Kerberos (win32)', function() {
97109
// by calling authGSSClientWrap with the "user" option.
98110
// const UPN = Buffer.from(upn, 'utf8').toString('utf8');
99111
const msg = Buffer.from(`\x01\x00\x00\x00${upn}`).toString('base64');
100-
krbClient.wrap(msg, {}, (err, custom) => {
112+
krbClient.wrap(msg, (err, custom) => {
101113
expect(err).to.not.exist;
102114
expect(custom).to.exist;
103115

@@ -126,4 +138,48 @@ describe('Kerberos (win32)', function() {
126138
);
127139
});
128140
});
141+
142+
it('should work from windows using promises', function() {
143+
return test.client.connect().then(client => {
144+
const db = client.db('$external');
145+
146+
return kerberos
147+
.initializeClient(service, { user: username, domain: realm, password })
148+
.then(krbClient => {
149+
return authenticate({ db, krbClient, start: true }).then(authResponse => {
150+
return krbClient.unwrap(authResponse.challenge).then(unwrapped => {
151+
// RFC-4752
152+
const challengeBytes = Buffer.from(unwrapped, 'base64');
153+
expect(challengeBytes).to.have.length(4);
154+
155+
// Manually create an authorization message and encrypt it. This
156+
// is the "no security layer" message as detailed in RFC-4752,
157+
// section 3.1, final paragraph. This is also the message created
158+
// by calling authGSSClientWrap with the "user" option.
159+
// const UPN = Buffer.from(upn, 'utf8').toString('utf8');
160+
const msg = Buffer.from(`\x01\x00\x00\x00${upn}`).toString('base64');
161+
return krbClient
162+
.wrap(msg)
163+
.then(custom => {
164+
expect(custom).to.exist;
165+
166+
// Wrap using unwrapped and user principal
167+
return krbClient.wrap(unwrapped, { user: upn });
168+
})
169+
.then(wrapped => {
170+
expect(wrapped).to.exist;
171+
return db.command({
172+
saslContinue: 1,
173+
conversationId: authResponse.conversationId,
174+
payload: wrapped
175+
});
176+
})
177+
.then(() => {
178+
expect(krbClient.username).to.exist;
179+
});
180+
});
181+
});
182+
});
183+
});
184+
});
129185
});

0 commit comments

Comments
 (0)