Skip to content

Commit d55925a

Browse files
Pierre Cauchoisgrs
authored andcommitted
BREAKING(sasl): make sasl mechanisms async
1 parent 473dc65 commit d55925a

File tree

2 files changed

+123
-19
lines changed

2 files changed

+123
-19
lines changed

lib/sasl.js

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ var PlainClient = function(username, password) {
7171
this.password = password;
7272
};
7373

74-
PlainClient.prototype.start = function() {
74+
PlainClient.prototype.start = function(callback) {
7575
var response = new Buffer(1 + this.username.length + 1 + this.password.length);
7676
response.writeUInt8(0, 0);
7777
response.write(this.username, 1);
7878
response.writeUInt8(0, 1 + this.username.length);
7979
response.write(this.password, 1 + this.username.length + 1);
80-
return response;
80+
callback(undefined, response);
8181
};
8282

8383
var AnonymousServer = function() {
@@ -94,11 +94,11 @@ var AnonymousClient = function(name) {
9494
this.username = name ? name : 'anonymous';
9595
};
9696

97-
AnonymousClient.prototype.start = function() {
97+
AnonymousClient.prototype.start = function(callback) {
9898
var response = new Buffer(1 + this.username.length);
9999
response.writeUInt8(0, 0);
100100
response.write(this.username, 1);
101-
return response;
101+
callback(undefined, response);
102102
};
103103

104104
var ExternalServer = function() {
@@ -114,20 +114,20 @@ var ExternalClient = function() {
114114
this.username = undefined;
115115
};
116116

117-
ExternalClient.prototype.start = function() {
118-
return '';
117+
ExternalClient.prototype.start = function(callback) {
118+
callback(undefined, '');
119119
};
120120

121-
ExternalClient.prototype.step = function() {
122-
return '';
121+
ExternalClient.prototype.step = function(callback) {
122+
callback(undefined, '');
123123
};
124124

125125
var XOAuth2Client = function(username, token) {
126126
this.username = username;
127127
this.token = token;
128128
};
129129

130-
XOAuth2Client.prototype.start = function() {
130+
XOAuth2Client.prototype.start = function(callback) {
131131
var response = new Buffer(this.username.length + this.token.length + 5 + 12 + 3);
132132
var count = 0;
133133
response.write('user=', count);
@@ -144,7 +144,7 @@ XOAuth2Client.prototype.start = function() {
144144
count += 1;
145145
response.writeUInt8(1, count);
146146
count += 1;
147-
return response;
147+
callback(response);
148148
};
149149

150150
/**
@@ -228,25 +228,41 @@ SaslClient.prototype.on_sasl_mechanisms = function (frame) {
228228
var mech = frame.performative.sasl_server_mechanisms[i];
229229
var f = this.mechanisms[mech];
230230
if (f) {
231-
this.mechanism = f();
231+
this.mechanism = typeof f === 'function' ? f() : f;
232232
this.mechanism_name = mech;
233233
}
234234
}
235235
if (this.mechanism) {
236-
var response = this.mechanism.start();
237-
var init = {'mechanism':this.mechanism_name,'initial_response':response};
238-
if (this.hostname) {
239-
init.hostname = this.hostname;
236+
var self = this;
237+
this.mechanism.start(function (err, response) {
238+
if (err) {
239+
self.failed = true;
240+
self.connection.sasl_failed('SASL mechanism init failed: ' + err);
241+
} else {
242+
var init = {'mechanism':self.mechanism_name,'initial_response':response};
243+
if (self.hostname) {
244+
init.hostname = self.hostname;
245+
}
246+
self.transport.encode(frames.sasl_frame(frames.sasl_init(init).described()));
247+
self.connection.output();
240248
}
241-
this.transport.encode(frames.sasl_frame(frames.sasl_init(init).described()));
249+
});
242250
} else {
243251
this.failed = true;
244252
this.connection.sasl_failed('No suitable mechanism; server supports ' + frame.performative.sasl_server_mechanisms);
245253
}
246254
};
247255
SaslClient.prototype.on_sasl_challenge = function (frame) {
248-
var response = this.mechanism.step(frame.performative.challenge);
249-
this.transport.encode(frames.sasl_frame(frames.sasl_response({'response':response}).described()));
256+
var self = this;
257+
this.mechanism.step(frame.performative.challenge, function (err, response) {
258+
if (err) {
259+
self.failed = true;
260+
self.connection.sasl_failed('SASL mechanism challenge failed: ' + err);
261+
} else {
262+
self.transport.encode(frames.sasl_frame(frames.sasl_response({'response':response}).described()));
263+
self.connection.output();
264+
}
265+
});
250266
};
251267
SaslClient.prototype.on_sasl_outcome = function (frame) {
252268
switch (frame.performative.code) {

test/sasl.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('sasl plain', function() {
4343
container.connect({username:'bob',password:'bob',port:listener.address().port}).on('connection_open', function(context) { context.connection.close(); done(); });
4444
});
4545
it('handles authentication failure', function(done: Function) {
46-
container.connect({username:'whatsit',password:'anyoldrubbish',port:listener.address().port}).on('connection_error', function(context) {
46+
container.connect({username:'whatsit',password:'anyoldrubbish',port:listener.address().port}).on('connection_error', function(context) {
4747
var error = context.connection.get_error();
4848
assert.equal(error.condition, 'amqp:unauthorized-access');
4949
done();
@@ -132,3 +132,91 @@ describe('sasl anonymous', function() {
132132
});
133133
});
134134
});
135+
136+
describe('user-provided sasl mechanism', function () {
137+
var container: rhea.Container, listener: any;
138+
var testClientSaslMechanism = {
139+
start: function (callback: Function) { callback(null, 'initialResponse'); },
140+
step: function (challenge: any, callback: Function) { callback (null, 'challengeResponse'); }
141+
};
142+
143+
beforeEach(function(done: Function) {
144+
container = rhea.create_container();
145+
container.on('disconnected', function () {});
146+
listener = container.listen({port:0});
147+
listener.on('listening', function() {
148+
done();
149+
});
150+
});
151+
152+
afterEach(function() {
153+
listener.close();
154+
});
155+
156+
it('calls start and step on the custom sasl mechanism', function (done: Function) {
157+
var startCalled = false;
158+
var stepCalled = false;
159+
var connectOptions = {
160+
sasl_mechanisms: {
161+
'CUSTOM': testClientSaslMechanism
162+
},
163+
port: listener.address().port
164+
}
165+
166+
container.sasl_server_mechanisms['CUSTOM'] = function () {
167+
return {
168+
outcome: undefined,
169+
start: function () {
170+
startCalled = true;
171+
return 'initialResponse';
172+
},
173+
step: function () {
174+
stepCalled = true;
175+
this.outcome = <any>true;
176+
return;
177+
}
178+
};
179+
};
180+
181+
container.connect(connectOptions).on('connection_open', function(context) {
182+
context.connection.close();
183+
assert(startCalled);
184+
assert(stepCalled);
185+
done();
186+
});
187+
});
188+
189+
it('handles authentication failure if the custom server sasl mechanism fails', function (done: Function) {
190+
var startCalled = false;
191+
var stepCalled = false;
192+
var connectOptions = {
193+
sasl_mechanisms: {
194+
'CUSTOM': testClientSaslMechanism
195+
},
196+
port: listener.address().port
197+
}
198+
199+
container.sasl_server_mechanisms['CUSTOM'] = function () {
200+
return {
201+
outcome: undefined,
202+
start: function () {
203+
startCalled = true;
204+
return 'initialResponse';
205+
},
206+
step: function () {
207+
stepCalled = true;
208+
this.outcome = <any>false;
209+
return;
210+
}
211+
};
212+
};
213+
214+
container.connect(connectOptions).on('connection_error', function(context) {
215+
var error = context.connection.get_error();
216+
assert.equal(error.condition, 'amqp:unauthorized-access');
217+
assert(startCalled);
218+
assert(stepCalled);
219+
done();
220+
});
221+
});
222+
});

0 commit comments

Comments
 (0)