GH-27 Support for abandon operations client side, and noop it server side
This commit is contained in:
parent
060bb2c55b
commit
75aba691f1
123
lib/client.js
123
lib/client.js
|
@ -21,6 +21,7 @@ var url = require('./url');
|
||||||
|
|
||||||
///--- Globals
|
///--- Globals
|
||||||
|
|
||||||
|
var AbandonRequest = messages.AbandonRequest;
|
||||||
var AddRequest = messages.AddRequest;
|
var AddRequest = messages.AddRequest;
|
||||||
var BindRequest = messages.BindRequest;
|
var BindRequest = messages.BindRequest;
|
||||||
var CompareRequest = messages.CompareRequest;
|
var CompareRequest = messages.CompareRequest;
|
||||||
|
@ -189,6 +190,38 @@ Client.prototype.bind = function(name, credentials, controls, callback, conn) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an abandon request to the LDAP server.
|
||||||
|
*
|
||||||
|
* The callback will be invoked as soon as the data is flushed out to the
|
||||||
|
* network, as there is never a response from abandon.
|
||||||
|
*
|
||||||
|
* @param {Number} messageID the messageID to abandon.
|
||||||
|
* @param {Control} controls (optional) either a Control or [Control].
|
||||||
|
* @param {Function} callback of the form f(err).
|
||||||
|
* @throws {TypeError} on invalid input.
|
||||||
|
*/
|
||||||
|
Client.prototype.abandon = function(messageID, controls, callback) {
|
||||||
|
if (typeof(messageID) !== 'number')
|
||||||
|
throw new TypeError('messageID (number) required');
|
||||||
|
if (typeof(controls) === 'function') {
|
||||||
|
callback = controls;
|
||||||
|
controls = [];
|
||||||
|
} else {
|
||||||
|
control = validateControls(controls);
|
||||||
|
}
|
||||||
|
if (typeof(callback) !== 'function')
|
||||||
|
throw new TypeError('callback (function) required');
|
||||||
|
|
||||||
|
var req = new AbandonRequest({
|
||||||
|
abandonID: messageID,
|
||||||
|
controls: controls
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._send(req, 'abandon', callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an entry to the LDAP server.
|
* Adds an entry to the LDAP server.
|
||||||
*
|
*
|
||||||
|
@ -583,49 +616,51 @@ Client.prototype._send = function(message, expect, callback, connection) {
|
||||||
|
|
||||||
// Now set up the callback in the messages table
|
// Now set up the callback in the messages table
|
||||||
message.messageID = conn.ldap.nextMessageID;
|
message.messageID = conn.ldap.nextMessageID;
|
||||||
conn.ldap.messages[message.messageID] = function(res) {
|
if (expect !== 'abandon') {
|
||||||
if (self.log.isDebugEnabled())
|
conn.ldap.messages[message.messageID] = function(res) {
|
||||||
self.log.debug('%s: response received: %j', conn.ldap.id, res.json);
|
if (self.log.isDebugEnabled())
|
||||||
|
self.log.debug('%s: response received: %j', conn.ldap.id, res.json);
|
||||||
|
|
||||||
var err = null;
|
var err = null;
|
||||||
|
|
||||||
if (res instanceof LDAPResult) {
|
if (res instanceof LDAPResult) {
|
||||||
delete conn.ldap.messages[message.messageID];
|
delete conn.ldap.messages[message.messageID];
|
||||||
|
|
||||||
if (expect.indexOf(res.status) === -1) {
|
if (expect.indexOf(res.status) === -1) {
|
||||||
err = errors.getError(res);
|
err = errors.getError(res);
|
||||||
|
if (typeof(callback) === 'function')
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
return callback.emit('error', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(callback) === 'function')
|
||||||
|
return callback(null, res);
|
||||||
|
|
||||||
|
callback.emit('end', res);
|
||||||
|
|
||||||
|
} else if (res instanceof SearchEntry) {
|
||||||
|
assert.ok(callback instanceof EventEmitter);
|
||||||
|
callback.emit('searchEntry', res);
|
||||||
|
|
||||||
|
} else if (res instanceof SearchReference) {
|
||||||
|
assert.ok(callback instanceof EventEmitter);
|
||||||
|
callback.emit('searchReference', res);
|
||||||
|
|
||||||
|
} else if (res instanceof Error) {
|
||||||
|
return callback(res);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
delete conn.ldap.messages[message.messageID];
|
||||||
|
|
||||||
|
err = new errors.ProtocolError(res.type);
|
||||||
if (typeof(callback) === 'function')
|
if (typeof(callback) === 'function')
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
return callback.emit('error', err);
|
callback.emit('error', err);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
if (typeof(callback) === 'function')
|
}
|
||||||
return callback(null, res);
|
|
||||||
|
|
||||||
callback.emit('end', res);
|
|
||||||
|
|
||||||
} else if (res instanceof SearchEntry) {
|
|
||||||
assert.ok(callback instanceof EventEmitter);
|
|
||||||
callback.emit('searchEntry', res);
|
|
||||||
|
|
||||||
} else if (res instanceof SearchReference) {
|
|
||||||
assert.ok(callback instanceof EventEmitter);
|
|
||||||
callback.emit('searchReference', res);
|
|
||||||
|
|
||||||
} else if (res instanceof Error) {
|
|
||||||
return callback(res);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
delete conn.ldap.messages[message.messageID];
|
|
||||||
|
|
||||||
err = new errors.ProtocolError(res.type);
|
|
||||||
if (typeof(callback) === 'function')
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
callback.emit('error', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Finally send some data
|
// Finally send some data
|
||||||
if (this.log.isDebugEnabled())
|
if (this.log.isDebugEnabled())
|
||||||
|
@ -633,9 +668,19 @@ Client.prototype._send = function(message, expect, callback, connection) {
|
||||||
|
|
||||||
// Note if this was an unbind, we just go ahead and end, since there
|
// Note if this was an unbind, we just go ahead and end, since there
|
||||||
// will never be a response
|
// will never be a response
|
||||||
return conn.write(message.toBer(), (expect === 'unbind' ? function() {
|
var _writeCb = null;
|
||||||
conn.end();
|
if (expect === 'abandon') {
|
||||||
} : null));
|
_writeCb = function() {
|
||||||
|
return callback();
|
||||||
|
};
|
||||||
|
} else if (expect === 'unbind') {
|
||||||
|
_writeCb = function() {
|
||||||
|
conn.end();
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
return conn.write(message.toBer(), _writeCb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
var asn1 = require('asn1');
|
||||||
|
|
||||||
|
var LDAPMessage = require('./message');
|
||||||
|
var LDAPResult = require('./result');
|
||||||
|
|
||||||
|
var dn = require('../dn');
|
||||||
|
var Attribute = require('../attribute');
|
||||||
|
var Protocol = require('../protocol');
|
||||||
|
|
||||||
|
///--- Globals
|
||||||
|
|
||||||
|
var Ber = asn1.Ber;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///--- API
|
||||||
|
|
||||||
|
function AbandonRequest(options) {
|
||||||
|
if (options) {
|
||||||
|
if (typeof(options) !== 'object')
|
||||||
|
throw new TypeError('options must be an object');
|
||||||
|
if (options.abandonID && typeof(options.abandonID) !== 'number')
|
||||||
|
throw new TypeError('abandonID must be a number');
|
||||||
|
} else {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
options.protocolOp = Protocol.LDAP_REQ_ABANDON;
|
||||||
|
LDAPMessage.call(this, options);
|
||||||
|
|
||||||
|
this.abandonID = options.abandonID || 0;
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.__defineGetter__('type', function() { return 'AbandonRequest'; });
|
||||||
|
}
|
||||||
|
util.inherits(AbandonRequest, LDAPMessage);
|
||||||
|
module.exports = AbandonRequest;
|
||||||
|
|
||||||
|
|
||||||
|
AbandonRequest.prototype._parse = function(ber, length) {
|
||||||
|
assert.ok(ber);
|
||||||
|
assert.ok(length);
|
||||||
|
|
||||||
|
// What a PITA - have to replicate ASN.1 integer logic to work around the
|
||||||
|
// way abandon is encoded and the way ldapjs framework handles "normal"
|
||||||
|
// messages
|
||||||
|
|
||||||
|
var buf = ber.buffer;
|
||||||
|
var offset = 0;
|
||||||
|
var value = 0;
|
||||||
|
|
||||||
|
var fb = buf[offset++];
|
||||||
|
value = fb & 0x7F;
|
||||||
|
for (var i = 1; i < length; i++) {
|
||||||
|
value <<= 8;
|
||||||
|
value |= (buf[offset++] & 0xff);
|
||||||
|
}
|
||||||
|
if ((fb & 0x80) == 0x80)
|
||||||
|
value = -value;
|
||||||
|
|
||||||
|
ber._offset += length;
|
||||||
|
|
||||||
|
this.abandonID = value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
AbandonRequest.prototype._toBer = function(ber) {
|
||||||
|
assert.ok(ber);
|
||||||
|
|
||||||
|
var i = this.abandonID;
|
||||||
|
var sz = 4;
|
||||||
|
|
||||||
|
while ((((i & 0xff800000) == 0) || ((i & 0xff800000) == 0xff800000)) &&
|
||||||
|
(sz > 1)) {
|
||||||
|
sz--;
|
||||||
|
i <<= 8;
|
||||||
|
}
|
||||||
|
assert.ok(sz <= 4);
|
||||||
|
|
||||||
|
while (sz-- > 0) {
|
||||||
|
ber.writeByte((i & 0xff000000) >> 24);
|
||||||
|
i <<= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ber;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
AbandonRequest.prototype._json = function(j) {
|
||||||
|
assert.ok(j);
|
||||||
|
|
||||||
|
j.abandonID = this.abandonID;
|
||||||
|
|
||||||
|
return j;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
var LDAPMessage = require('./result');
|
||||||
|
var Protocol = require('../protocol');
|
||||||
|
|
||||||
|
|
||||||
|
///--- API
|
||||||
|
// Stub this out
|
||||||
|
|
||||||
|
function AbandonResponse(options) {
|
||||||
|
if (!options)
|
||||||
|
options = {};
|
||||||
|
if (typeof(options) !== 'object')
|
||||||
|
throw new TypeError('options must be an object');
|
||||||
|
|
||||||
|
options.protocolOp = 0;
|
||||||
|
LDAPMessage.call(this, options);
|
||||||
|
this.__defineGetter__('type', function() { return 'AbandonResponse'; });
|
||||||
|
}
|
||||||
|
util.inherits(AbandonResponse, LDAPMessage);
|
||||||
|
module.exports = AbandonResponse;
|
||||||
|
|
||||||
|
|
||||||
|
AbandonResponse.prototype.end = function(status) {};
|
||||||
|
|
||||||
|
|
||||||
|
AbandonResponse.prototype._json = function(j) {
|
||||||
|
return j;
|
||||||
|
};
|
|
@ -4,6 +4,8 @@ var LDAPMessage = require('./message');
|
||||||
var LDAPResult = require('./result');
|
var LDAPResult = require('./result');
|
||||||
var Parser = require('./parser');
|
var Parser = require('./parser');
|
||||||
|
|
||||||
|
var AbandonRequest = require('./abandon_request');
|
||||||
|
var AbandonResponse = require('./abandon_response');
|
||||||
var AddRequest = require('./add_request');
|
var AddRequest = require('./add_request');
|
||||||
var AddResponse = require('./add_response');
|
var AddResponse = require('./add_response');
|
||||||
var BindRequest = require('./bind_request');
|
var BindRequest = require('./bind_request');
|
||||||
|
@ -35,6 +37,8 @@ module.exports = {
|
||||||
LDAPResult: LDAPResult,
|
LDAPResult: LDAPResult,
|
||||||
Parser: Parser,
|
Parser: Parser,
|
||||||
|
|
||||||
|
AbandonRequest: AbandonRequest,
|
||||||
|
AbandonResponse: AbandonResponse,
|
||||||
AddRequest: AddRequest,
|
AddRequest: AddRequest,
|
||||||
AddResponse: AddResponse,
|
AddResponse: AddResponse,
|
||||||
BindRequest: BindRequest,
|
BindRequest: BindRequest,
|
||||||
|
|
|
@ -6,6 +6,7 @@ var util = require('util');
|
||||||
|
|
||||||
var asn1 = require('asn1');
|
var asn1 = require('asn1');
|
||||||
|
|
||||||
|
var AbandonRequest = require('./abandon_request');
|
||||||
var AddRequest = require('./add_request');
|
var AddRequest = require('./add_request');
|
||||||
var AddResponse = require('./add_response');
|
var AddResponse = require('./add_response');
|
||||||
var BindRequest = require('./bind_request');
|
var BindRequest = require('./bind_request');
|
||||||
|
@ -148,6 +149,10 @@ Parser.prototype._newMessage = function(ber) {
|
||||||
var Message;
|
var Message;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
||||||
|
case Protocol.LDAP_REQ_ABANDON:
|
||||||
|
Message = AbandonRequest;
|
||||||
|
break;
|
||||||
|
|
||||||
case Protocol.LDAP_REQ_ADD:
|
case Protocol.LDAP_REQ_ADD:
|
||||||
Message = AddRequest;
|
Message = AddRequest;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -16,6 +16,7 @@ var Protocol = require('./protocol');
|
||||||
var logStub = require('./log_stub');
|
var logStub = require('./log_stub');
|
||||||
|
|
||||||
var Parser = require('./messages').Parser;
|
var Parser = require('./messages').Parser;
|
||||||
|
var AbandonResponse = require('./messages/abandon_response');
|
||||||
var AddResponse = require('./messages/add_response');
|
var AddResponse = require('./messages/add_response');
|
||||||
var BindResponse = require('./messages/bind_response');
|
var BindResponse = require('./messages/bind_response');
|
||||||
var CompareResponse = require('./messages/compare_response');
|
var CompareResponse = require('./messages/compare_response');
|
||||||
|
@ -79,7 +80,8 @@ function getResponse(req) {
|
||||||
Response = BindResponse;
|
Response = BindResponse;
|
||||||
break;
|
break;
|
||||||
case Protocol.LDAP_REQ_ABANDON:
|
case Protocol.LDAP_REQ_ABANDON:
|
||||||
return; // Noop
|
Response = AbandonResponse;
|
||||||
|
break;
|
||||||
case Protocol.LDAP_REQ_ADD:
|
case Protocol.LDAP_REQ_ADD:
|
||||||
Response = AddResponse;
|
Response = AddResponse;
|
||||||
break;
|
break;
|
||||||
|
@ -133,6 +135,16 @@ function defaultHandler(req, res, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function defaultAbandonHandler(req, res, next) {
|
||||||
|
assert.ok(req);
|
||||||
|
assert.ok(res);
|
||||||
|
assert.ok(next);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function defaultUnbindHandler(req, res, next) {
|
function defaultUnbindHandler(req, res, next) {
|
||||||
assert.ok(req);
|
assert.ok(req);
|
||||||
assert.ok(res);
|
assert.ok(res);
|
||||||
|
@ -333,7 +345,6 @@ function Server(options) {
|
||||||
res.requestDN = req.dn;
|
res.requestDN = req.dn;
|
||||||
|
|
||||||
var chain = self._getHandlerChain(req);
|
var chain = self._getHandlerChain(req);
|
||||||
//.concat(this._postChain || []);
|
|
||||||
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
return function(err) {
|
return function(err) {
|
||||||
|
@ -650,6 +661,7 @@ Server.prototype.after = function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// All these just reexpose the requisite net.Server APIs
|
// All these just reexpose the requisite net.Server APIs
|
||||||
Server.prototype.listen = function(port, host, callback) {
|
Server.prototype.listen = function(port, host, callback) {
|
||||||
if (!port)
|
if (!port)
|
||||||
|
@ -736,7 +748,7 @@ Server.prototype._getHandlerChain = function(req) {
|
||||||
for (var r in routes) {
|
for (var r in routes) {
|
||||||
if (routes.hasOwnProperty(r)) {
|
if (routes.hasOwnProperty(r)) {
|
||||||
var route = routes[r];
|
var route = routes[r];
|
||||||
// Special cases are exops and unbinds, handle those first.
|
// Special cases are abandons, exops and unbinds, handle those first.
|
||||||
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
|
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
|
||||||
if (r !== req.requestName)
|
if (r !== req.requestName)
|
||||||
continue;
|
continue;
|
||||||
|
@ -758,6 +770,11 @@ Server.prototype._getHandlerChain = function(req) {
|
||||||
backend: routes['unbind'] ? routes['unbind'].backend : self,
|
backend: routes['unbind'] ? routes['unbind'].backend : self,
|
||||||
handlers: getUnbindChain()
|
handlers: getUnbindChain()
|
||||||
};
|
};
|
||||||
|
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) {
|
||||||
|
return {
|
||||||
|
backend: self,
|
||||||
|
handlers: [defaultAbandonHandler]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!route[op])
|
if (!route[op])
|
||||||
|
|
14
package.json
14
package.json
|
@ -17,15 +17,15 @@
|
||||||
"node": ">=0.4"
|
"node": ">=0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asn1": "~0.1.7",
|
"asn1": "0.1.8",
|
||||||
"buffertools": "~1.0.3",
|
"buffertools": "1.0.5",
|
||||||
"dtrace-provider": "~0.0.3",
|
"dtrace-provider": "0.0.3",
|
||||||
"nopt": "~1.0.10",
|
"nopt": "1.0.10",
|
||||||
"sprintf": "~0.1.1"
|
"sprintf": "0.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tap": "~0.0.9",
|
"tap": "0.0.12",
|
||||||
"node-uuid": "~1.2.0"
|
"node-uuid": "1.2.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "./node_modules/.bin/tap ./tst"
|
"test": "./node_modules/.bin/tap ./tst"
|
||||||
|
|
|
@ -541,6 +541,14 @@ test('GH-24 attribute selection of *', function(t) {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('abandon (GH-27)', function(t) {
|
||||||
|
client.abandon(401876543, function(err) {
|
||||||
|
t.ifError(err);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
test('shutdown', function(t) {
|
test('shutdown', function(t) {
|
||||||
client.unbind(function() {
|
client.unbind(function() {
|
||||||
server.on('close', function() {
|
server.on('close', function() {
|
||||||
|
|
Loading…
Reference in New Issue