Refactoring server into something less sucky
This commit is contained in:
parent
1042048aaf
commit
7706cd5e4e
|
@ -50,8 +50,10 @@ function Change(options) {
|
|||
return self._modification;
|
||||
});
|
||||
this.__defineSetter__('modification', function(attr) {
|
||||
if (Attribute.isAttribute(attr))
|
||||
if (Attribute.isAttribute(attr)) {
|
||||
self._modification = attr;
|
||||
return;
|
||||
}
|
||||
Object.keys(attr).forEach(function(k) {
|
||||
var _attr = new Attribute({type: k});
|
||||
if (Array.isArray(attr[k])) {
|
||||
|
@ -67,7 +69,7 @@ function Change(options) {
|
|||
this.__defineGetter__('json', function() {
|
||||
return {
|
||||
operation: self.operation,
|
||||
modification: self.modification ? self.modification.json : {}
|
||||
modification: self._modification ? self._modification.json : {}
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -269,6 +269,7 @@ Client.prototype.bind = function(name, credentials, controls, callback) {
|
|||
|
||||
if (++finished >= self.connections.length && !cbIssued) {
|
||||
cbIssued = true;
|
||||
self.emit('bind');
|
||||
return callback(null, res);
|
||||
}
|
||||
}
|
||||
|
@ -642,6 +643,7 @@ Client.prototype.unbind = function(callback) {
|
|||
var req = new UnbindRequest();
|
||||
var finished = 0;
|
||||
var cbIssued = false;
|
||||
|
||||
function _callback(err, res) {
|
||||
if (err) {
|
||||
if (!cbIssued) {
|
||||
|
@ -652,6 +654,7 @@ Client.prototype.unbind = function(callback) {
|
|||
|
||||
if (++finished >= self.connections.length && !cbIssued) {
|
||||
cbIssued = true;
|
||||
self.emit('unbind');
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
|
@ -735,7 +738,6 @@ Client.prototype._send = function(message, expect, callback, conn) {
|
|||
// will never be a response
|
||||
return conn.write(message.toBer(), (expect === 'unbind' ? function() {
|
||||
conn.on('end', function() {
|
||||
self.emit('unbind');
|
||||
return callback();
|
||||
});
|
||||
conn.end();
|
||||
|
|
22
lib/index.js
22
lib/index.js
|
@ -1,18 +1,19 @@
|
|||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
var Client = require('./client');
|
||||
var dn = require('./dn');
|
||||
var errors = require('./errors');
|
||||
var filters = require('./filters');
|
||||
var messages = require('./messages');
|
||||
var server = require('./server');
|
||||
var logStub = require('./log_stub');
|
||||
var url = require('./url');
|
||||
|
||||
var Attribute = require('./attribute');
|
||||
var Change = require('./change');
|
||||
var Control = require('./control');
|
||||
var Protocol = require('./protocol');
|
||||
var Server = require('./server');
|
||||
|
||||
var dn = require('./dn');
|
||||
var errors = require('./errors');
|
||||
var filters = require('./filters');
|
||||
var logStub = require('./log_stub');
|
||||
var messages = require('./messages');
|
||||
var url = require('./url');
|
||||
|
||||
|
||||
|
||||
/// Hack a few things we need (i.e., "monkey patch" the prototype)
|
||||
|
@ -46,7 +47,10 @@ module.exports = {
|
|||
return new Client(options);
|
||||
},
|
||||
|
||||
createServer: server.createServer,
|
||||
Server: Server,
|
||||
createServer: function(options) {
|
||||
return new Server(options);
|
||||
},
|
||||
|
||||
dn: dn,
|
||||
DN: dn.DN,
|
||||
|
|
880
lib/server.js
880
lib/server.js
|
@ -36,32 +36,33 @@ var BerReader = asn1.BerReader;
|
|||
|
||||
///--- Helpers
|
||||
|
||||
function setupConnection(server, c, config) {
|
||||
assert.ok(server);
|
||||
assert.ok(c);
|
||||
assert.ok(config);
|
||||
function mergeFunctionArgs(argv, start, end) {
|
||||
assert.ok(argv);
|
||||
|
||||
c.ldap = {
|
||||
id: c.remoteAddress + ':' + c.remotePort,
|
||||
config: config
|
||||
};
|
||||
if (!start)
|
||||
start = 0;
|
||||
if (!end)
|
||||
end = argv.length;
|
||||
|
||||
c.addListener('timeout', function() {
|
||||
server.log.trace('%s timed out', c.ldap.id);
|
||||
c.destroy();
|
||||
});
|
||||
c.addListener('end', function() {
|
||||
server.log.trace('%s shutdown', c.ldap.id);
|
||||
});
|
||||
c.addListener('error', function(err) {
|
||||
server.log.warn('%s unexpected connection error', c.ldap.id, err);
|
||||
c.destroy();
|
||||
});
|
||||
c.addListener('close', function(had_err) {
|
||||
server.log.trace('%s close; had_err=%j', c.ldap.id, had_err);
|
||||
c.destroy();
|
||||
});
|
||||
return c;
|
||||
var handlers = [];
|
||||
|
||||
for (var i = start; i < end; i++) {
|
||||
if (argv[i] instanceof Array) {
|
||||
var arr = argv[i];
|
||||
for (var j = 0; j < arr.length; j++) {
|
||||
if (!(arr[j] instanceof Function)) {
|
||||
throw new TypeError('Invalid argument type: ' + typeof(arr[j]));
|
||||
}
|
||||
handlers.push(arr[j]);
|
||||
}
|
||||
} else if (argv[i] instanceof Function) {
|
||||
handlers.push(argv[i]);
|
||||
} else {
|
||||
throw new TypeError('Invalid argument type: ' + typeof(argv[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
|
@ -128,6 +129,16 @@ function defaultHandler(req, res, next) {
|
|||
}
|
||||
|
||||
|
||||
function defaultUnbindHandler(req, res, next) {
|
||||
assert.ok(req);
|
||||
assert.ok(res);
|
||||
assert.ok(next);
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
function noSuffixHandler(req, res, next) {
|
||||
assert.ok(req);
|
||||
assert.ok(res);
|
||||
|
@ -150,360 +161,513 @@ function noExOpHandler(req, res, next) {
|
|||
}
|
||||
|
||||
|
||||
function getHandlerChain(server, req) {
|
||||
assert.ok(server);
|
||||
assert.ok(req);
|
||||
|
||||
var backend;
|
||||
var handlers;
|
||||
var matched = false;
|
||||
for (var r in server.routes) {
|
||||
if (server.routes.hasOwnProperty(r)) {
|
||||
|
||||
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
|
||||
if (r === req.requestName)
|
||||
matched = true;
|
||||
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
|
||||
matched = true;
|
||||
} else {
|
||||
if (req.dn) {
|
||||
if (r === req.dn.toString()) {
|
||||
matched = true;
|
||||
} else if (server.routes[r]._dn &&
|
||||
server.routes[r]._dn.parentOf(req.dn)) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!matched)
|
||||
continue;
|
||||
|
||||
switch (req.protocolOp) {
|
||||
case Protocol.LDAP_REQ_BIND:
|
||||
handlers = server.routes[r]._bind;
|
||||
break;
|
||||
|
||||
case Protocol.LDAP_REQ_ABANDON:
|
||||
return; // Noop
|
||||
|
||||
case Protocol.LDAP_REQ_ADD:
|
||||
handlers = server.routes[r]._add;
|
||||
break;
|
||||
|
||||
case Protocol.LDAP_REQ_COMPARE:
|
||||
handlers = server.routes[r]._compare;
|
||||
break;
|
||||
|
||||
case Protocol.LDAP_REQ_DELETE:
|
||||
handlers = server.routes[r]._del;
|
||||
break;
|
||||
|
||||
case Protocol.LDAP_REQ_EXTENSION:
|
||||
handlers = server.routes[r]._exop;
|
||||
break;
|
||||
|
||||
case Protocol.LDAP_REQ_MODIFY:
|
||||
handlers = server.routes[r]._modify;
|
||||
break;
|
||||
|
||||
case Protocol.LDAP_REQ_MODRDN:
|
||||
handlers = server.routes[r]._modifyDN;
|
||||
break;
|
||||
|
||||
case Protocol.LDAP_REQ_SEARCH:
|
||||
handlers = server.routes[r]._search;
|
||||
break;
|
||||
|
||||
case Protocol.LDAP_REQ_UNBIND:
|
||||
if (server.routes['unbind'])
|
||||
handlers = server.routes['unbind']._unbind;
|
||||
break;
|
||||
|
||||
default:
|
||||
server.log.warn('Unimplemented server method: %s', req.type);
|
||||
return c.destroy();
|
||||
}
|
||||
}
|
||||
if (handlers) {
|
||||
backend = server.routes[r]._backend;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handlers) {
|
||||
backend = server;
|
||||
if (matched) {
|
||||
server.log.warn('No handler registered for %s:%s, running default',
|
||||
req.type, req.dn.toString());
|
||||
handlers = [defaultHandler];
|
||||
} else {
|
||||
server.log.trace('%s does not map to a known suffix/oid',
|
||||
req.dn.toString());
|
||||
handlers = [req.protocolOp !== Protocol.LDAP_REQ_EXTENSION ?
|
||||
noSuffixHandler : noExOpHandler];
|
||||
}
|
||||
}
|
||||
|
||||
assert.ok(backend);
|
||||
assert.ok(handlers);
|
||||
assert.ok(handlers instanceof Array);
|
||||
assert.ok(handlers.length);
|
||||
|
||||
return {
|
||||
backend: backend,
|
||||
handlers: handlers
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function addHandlers(server) {
|
||||
assert.ok(server);
|
||||
assert.ok(server.log);
|
||||
|
||||
var log = server.log;
|
||||
|
||||
var ops = [ // We don't support abandon.
|
||||
'add',
|
||||
'bind',
|
||||
'compare',
|
||||
'del',
|
||||
'exop',
|
||||
'modify',
|
||||
'modifyDN',
|
||||
'search',
|
||||
'unbind'
|
||||
];
|
||||
|
||||
function processHandlerChain(chain) {
|
||||
if (!chain)
|
||||
return [defaultHandler];
|
||||
|
||||
if (chain instanceof Array) {
|
||||
if (!chain.length)
|
||||
return [defaultHandler];
|
||||
|
||||
chain.forEach(function(f) {
|
||||
if (typeof(f) !== 'function')
|
||||
throw new TypeError('[function(req, res, next)] required');
|
||||
});
|
||||
|
||||
return chain;
|
||||
} else if (typeof(chain) === 'function') {
|
||||
return [chain];
|
||||
}
|
||||
|
||||
throw new TypeError('[function(req, res, next)] required');
|
||||
}
|
||||
|
||||
server.routes = {};
|
||||
|
||||
ops.forEach(function(o) {
|
||||
var op = '_' + o;
|
||||
server[o] = function(name, handler) {
|
||||
if (o === 'unbind') {
|
||||
if (typeof(name === 'function')) {
|
||||
handler = name;
|
||||
name = 'unbind';
|
||||
}
|
||||
}
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (!handler || typeof(handler) !== 'function')
|
||||
throw new TypeError('[function(req, res, next)] required');
|
||||
|
||||
// Do this first so it will throw
|
||||
var _dn = null;
|
||||
if (o !== 'exop' && o !== 'unbind') {
|
||||
_dn = dn.parse(name);
|
||||
name = _dn.toString();
|
||||
}
|
||||
|
||||
if (!server.routes[name])
|
||||
server.routes[name] = {};
|
||||
if (!server.routes[name]._backend)
|
||||
server.routes[name]._backend = server;
|
||||
|
||||
server.routes[name][op] = processHandlerChain(handler);
|
||||
server.routes[name]._dn = _dn;
|
||||
if (log.isTraceEnabled()) {
|
||||
var _names = [];
|
||||
server.routes[name][op].forEach(function(f) {
|
||||
_names.push(f.name || 'Anonymous Function');
|
||||
});
|
||||
log.trace('%s(%s) -> %s', o, name, _names);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
server.mount = function(name, backend) {
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (!backend || typeof(backend) !== 'object')
|
||||
throw new TypeError('backend (object) required');
|
||||
if (!backend.name)
|
||||
throw new TypeError('backend is not a valid LDAP Backend');
|
||||
if (!backend.register || typeof(backend.register) !== 'function')
|
||||
throw new TypeError('backend is not a valid LDAP Backend');
|
||||
|
||||
var _dn = null;
|
||||
// Do this first so it will throw
|
||||
_dn = dn.parse(name);
|
||||
name = _dn.toString();
|
||||
|
||||
ops.forEach(function(o) {
|
||||
if (o === 'exop' || o === 'unbind')
|
||||
return;
|
||||
|
||||
var op = '_' + o;
|
||||
|
||||
if (!server.routes[name])
|
||||
server.routes[name] = {};
|
||||
if (!server.routes[name]._backend)
|
||||
server.routes[name]._backend = backend;
|
||||
|
||||
server.routes[name][op] = processHandlerChain(backend.register(o));
|
||||
if (log.isTraceEnabled()) {
|
||||
var _names = [];
|
||||
server.routes[name][op].forEach(function(f) { _names.push(f.name); });
|
||||
log.trace('%s(%s) -> %s', o, name, _names);
|
||||
}
|
||||
});
|
||||
|
||||
server.routes[name]._dn = _dn;
|
||||
|
||||
log.info('%s mounted at %s', server.routes[name]._backend.toString(), dn);
|
||||
|
||||
return server;
|
||||
};
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///--- API
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Constructs a new server that you can call .listen() on, in the various
|
||||
* forms node supports. You need to first assign some handlers to the various
|
||||
* LDAP operations however.
|
||||
*
|
||||
* The options object currently only takes a certificate/private key, and a
|
||||
* log4js handle.
|
||||
*
|
||||
* This object exposes the following events:
|
||||
* - 'error'
|
||||
* - 'close'
|
||||
*
|
||||
* @param {Object} options (optional) parameterization object.
|
||||
* @throws {TypeError} on bad input.
|
||||
*/
|
||||
function Server(options) {
|
||||
if (options) {
|
||||
if (typeof(options) !== 'object')
|
||||
throw new TypeError('options (object) required');
|
||||
if (options.log4js && typeof(options.log4js) !== 'object')
|
||||
throw new TypeError('options.log4s must be an object');
|
||||
|
||||
createServer: function(options) {
|
||||
if (options) {
|
||||
if (typeof(options) !== 'object')
|
||||
throw new TypeError('options (object) required');
|
||||
if (options.log4js && typeof(options.log4js) !== 'object')
|
||||
throw new TypeError('options.log4s must be an object');
|
||||
|
||||
if (options.certificate || options.key) {
|
||||
if (!(options.certificate && options.key) ||
|
||||
typeof(options.certificate) !== 'string' ||
|
||||
typeof(options.key) !== 'string') {
|
||||
throw new TypeError('options.certificate and options.key (string) ' +
|
||||
'are both required for TLS');
|
||||
}
|
||||
if (options.certificate || options.key) {
|
||||
if (!(options.certificate && options.key) ||
|
||||
typeof(options.certificate) !== 'string' ||
|
||||
typeof(options.key) !== 'string') {
|
||||
throw new TypeError('options.certificate and options.key (string) ' +
|
||||
'are both required for TLS');
|
||||
}
|
||||
} else {
|
||||
options = {};
|
||||
}
|
||||
} else {
|
||||
options = {};
|
||||
}
|
||||
var self = this;
|
||||
if (!options.log4js)
|
||||
options.log4js = logStub;
|
||||
|
||||
var server;
|
||||
EventEmitter.call(this, options);
|
||||
|
||||
function newConnection(c) {
|
||||
assert.ok(c);
|
||||
var log = this.log = options.log4js.getLogger('LDAPServer');
|
||||
|
||||
if (c.type === 'unix' && server.type === 'unix') {
|
||||
c.remoteAddress = server.path;
|
||||
c.remotePort = c.fd;
|
||||
}
|
||||
function setupConnection(c) {
|
||||
assert.ok(c);
|
||||
|
||||
assert.ok(c.remoteAddress);
|
||||
assert.ok(c.remotePort);
|
||||
|
||||
setupConnection(server, c, options);
|
||||
if (server.log.isTraceEnabled())
|
||||
server.log.trace('new connection from %s', c.ldap.id);
|
||||
|
||||
c.parser = new Parser({
|
||||
log4js: server.log4js
|
||||
});
|
||||
c.parser.on('message', function(req) {
|
||||
assert.ok(req);
|
||||
|
||||
req.connection = c;
|
||||
req.logId = c.remoteAddress + '::' + req.messageID;
|
||||
|
||||
if (server.log.isDebugEnabled())
|
||||
server.log.debug('%s: message received: req=%j', c.ldap.id, req.json);
|
||||
|
||||
var res = getResponse(req);
|
||||
if (!res) {
|
||||
server.log.warn('Unimplemented server method: %s', req.type);
|
||||
c.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
var chain = getHandlerChain(server, req);
|
||||
|
||||
var i = 0;
|
||||
return function(err) {
|
||||
if (err) {
|
||||
res.status = err.code || errors.LDAP_OPERATIONS_ERROR;
|
||||
res.matchedDN = err.dn ? err.dn.toString() : req.dn.toString();
|
||||
res.errorMessage = err.message || '';
|
||||
return res.end();
|
||||
}
|
||||
|
||||
var next = arguments.callee;
|
||||
if (chain.handlers[i])
|
||||
return chain.handlers[i++].call(chain.backend, req, res, next);
|
||||
}();
|
||||
});
|
||||
|
||||
c.parser.on('protocolError', function(err, messageID) {
|
||||
server.log.warn('%s sent invalid protocol message', c.ldap.id, err);
|
||||
// TODO (mcavage) deal with this
|
||||
// send an unsolicited notification
|
||||
c.destroy();
|
||||
});
|
||||
c.parser.on('error', function(err) {
|
||||
server.log.error('Exception happened parsing for %s: %s',
|
||||
c.ldap.id, err.stack);
|
||||
c.destroy();
|
||||
});
|
||||
c.on('data', function(data) {
|
||||
assert.ok(data);
|
||||
if (server.log.isTraceEnabled())
|
||||
server.log.trace('data on %s: %s', c.ldap.id, util.inspect(data));
|
||||
c.parser.write(data);
|
||||
});
|
||||
|
||||
}; // end newConnection
|
||||
|
||||
var secure = options.certificate && options.key;
|
||||
|
||||
if (secure) {
|
||||
server = tls.createServer(options, newConnection);
|
||||
} else {
|
||||
server = net.createServer(newConnection);
|
||||
}
|
||||
|
||||
server.log4js = options.log4js || logStub;
|
||||
|
||||
server.ldap = {
|
||||
c.ldap = {
|
||||
id: c.remoteAddress + ':' + c.remotePort,
|
||||
config: options
|
||||
};
|
||||
c.addListener('timeout', function() {
|
||||
log.trace('%s timed out', c.ldap.id);
|
||||
c.destroy();
|
||||
});
|
||||
c.addListener('end', function() {
|
||||
log.trace('%s shutdown', c.ldap.id);
|
||||
});
|
||||
c.addListener('error', function(err) {
|
||||
log.warn('%s unexpected connection error', c.ldap.id, err);
|
||||
c.destroy();
|
||||
});
|
||||
c.addListener('close', function(had_err) {
|
||||
log.trace('%s close; had_err=%j', c.ldap.id, had_err);
|
||||
c.end();
|
||||
});
|
||||
return c;
|
||||
}
|
||||
|
||||
server.__defineGetter__('log', function() {
|
||||
if (!server._log)
|
||||
server._log = server.log4js.getLogger('LDAPServer');
|
||||
function newConnection(c) {
|
||||
if (c.type === 'unix') {
|
||||
c.remoteAddress = self.server.path;
|
||||
c.remotePort = c.fd;
|
||||
}
|
||||
|
||||
return server._log;
|
||||
setupConnection(c);
|
||||
if (log.isTraceEnabled())
|
||||
log.trace('new connection from %s', c.ldap.id);
|
||||
|
||||
c.parser = new Parser({
|
||||
log4js: options.log4js
|
||||
});
|
||||
c.parser.on('message', function(req) {
|
||||
req.connection = c;
|
||||
req.logId = c.remoteAddress + '::' + req.messageID;
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
log.debug('%s: message received: req=%j', c.ldap.id, req.json);
|
||||
|
||||
var res = getResponse(req);
|
||||
if (!res) {
|
||||
log.warn('Unimplemented server method: %s', req.type);
|
||||
c.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
var chain = self._getHandlerChain(req);
|
||||
|
||||
var i = 0;
|
||||
return function(err) {
|
||||
if (err) {
|
||||
res.status = err.code || errors.LDAP_OPERATIONS_ERROR;
|
||||
res.matchedDN = err.dn ? err.dn.toString() : req.dn.toString();
|
||||
res.errorMessage = err.message || '';
|
||||
return res.end();
|
||||
}
|
||||
|
||||
var next = arguments.callee;
|
||||
if (chain.handlers[i])
|
||||
return chain.handlers[i++].call(chain.backend, req, res, next);
|
||||
}();
|
||||
});
|
||||
|
||||
addHandlers(server);
|
||||
c.parser.on('protocolError', function(err, messageID) {
|
||||
log.warn('%s sent invalid protocol message', c.ldap.id, err);
|
||||
c.destroy();
|
||||
});
|
||||
c.parser.on('error', function(err) {
|
||||
log.error('Exception happened parsing for %s: %s',
|
||||
c.ldap.id, err.stack);
|
||||
c.destroy();
|
||||
});
|
||||
c.on('data', function(data) {
|
||||
if (log.isTraceEnabled())
|
||||
log.trace('data on %s: %s', c.ldap.id, util.inspect(data));
|
||||
c.parser.write(data);
|
||||
});
|
||||
|
||||
return server;
|
||||
}; // end newConnection
|
||||
|
||||
this.routes = {};
|
||||
if (options.certificate && options.key) {
|
||||
this.server = tls.createServer(options, newConnection);
|
||||
} else {
|
||||
this.server = net.createServer(newConnection);
|
||||
}
|
||||
this.server.log4js = options.log4js;
|
||||
this.server.ldap = {
|
||||
config: options
|
||||
};
|
||||
this.server.on('close', function() {
|
||||
self.emit('close');
|
||||
});
|
||||
this.server.on('error', function(err) {
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
this.__defineGetter__('maxConnections', function() {
|
||||
return self.server.maxConnections;
|
||||
});
|
||||
this.__defineSetter__('maxConnections', function(val) {
|
||||
self.server.maxConnections = val;
|
||||
});
|
||||
this.__defineGetter__('connections', function() {
|
||||
return self.server.connections;
|
||||
});
|
||||
}
|
||||
util.inherits(Server, EventEmitter);
|
||||
module.exports = Server;
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP add method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.add = function(name) {
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (arguments.length < 2)
|
||||
throw new TypeError('name and at least one handler required');
|
||||
|
||||
var route = this._getRoute(dn.parse(name));
|
||||
route['0x' + Protocol.LDAP_REQ_ADD.toString(16)] =
|
||||
mergeFunctionArgs(Array.prototype.slice.call(arguments, 1));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP bind method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.bind = function(name) {
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (arguments.length < 2)
|
||||
throw new TypeError('name and at least one handler required');
|
||||
|
||||
var route = this._getRoute(dn.parse(name));
|
||||
route['0x' + Protocol.LDAP_REQ_BIND.toString(16)] =
|
||||
mergeFunctionArgs(Array.prototype.slice.call(arguments, 1));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP compare method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.compare = function(name) {
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (arguments.length < 2)
|
||||
throw new TypeError('name and at least one handler required');
|
||||
|
||||
var route = this._getRoute(dn.parse(name));
|
||||
route['0x' + Protocol.LDAP_REQ_COMPARE.toString(16)] =
|
||||
mergeFunctionArgs(Array.prototype.slice.call(arguments, 1));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP delete method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.del = function(name) {
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (arguments.length < 2)
|
||||
throw new TypeError('name and at least one handler required');
|
||||
|
||||
var route = this._getRoute(dn.parse(name));
|
||||
route['0x' + Protocol.LDAP_REQ_DELETE.toString(16)] =
|
||||
mergeFunctionArgs(Array.prototype.slice.call(arguments, 1));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP exop method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name OID to assign this handler chain to.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input.
|
||||
*/
|
||||
Server.prototype.exop = function(name) {
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (arguments.length < 2)
|
||||
throw new TypeError('name and at least one handler required');
|
||||
|
||||
var route = this._getRoute(name, this.server);
|
||||
route['0x' + Protocol.LDAP_REQ_EXTENSION.toString(16)] =
|
||||
mergeFunctionArgs(Array.prototype.slice.call(arguments, 1));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP modify method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.modify = function(name) {
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (arguments.length < 2)
|
||||
throw new TypeError('name and at least one handler required');
|
||||
|
||||
var route = this._getRoute(dn.parse(name));
|
||||
route['0x' + Protocol.LDAP_REQ_MODIFY.toString(16)] =
|
||||
mergeFunctionArgs(Array.prototype.slice.call(arguments, 1));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP modifyDN method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.modifyDN = function(name) {
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (arguments.length < 2)
|
||||
throw new TypeError('name and at least one handler required');
|
||||
|
||||
var route = this._getRoute(dn.parse(name));
|
||||
route['0x' + Protocol.LDAP_REQ_MODRDN.toString(16)] =
|
||||
mergeFunctionArgs(Array.prototype.slice.call(arguments, 1));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP search method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.search = function(name) {
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (arguments.length < 2)
|
||||
throw new TypeError('name and at least one handler required');
|
||||
|
||||
var route = this._getRoute(dn.parse(name));
|
||||
route['0x' + Protocol.LDAP_REQ_SEARCH.toString(16)] =
|
||||
mergeFunctionArgs(Array.prototype.slice.call(arguments, 1));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP unbind method.
|
||||
*
|
||||
* This method is different than the others and takes no mount point, as unbind
|
||||
* is a connection-wide operation, not constrianed to part of the DIT.
|
||||
*
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.unbind = function() {
|
||||
if (arguments.length < 1)
|
||||
throw new TypeError('at least one handler required');
|
||||
|
||||
var route = this._getRoute('unbind');
|
||||
route['0x' + Protocol.LDAP_REQ_UNBIND.toString(16)] =
|
||||
mergeFunctionArgs(Array.prototype.slice.call(arguments, 0));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* It's likely you'll write an entire backend for LDAP that does a series
|
||||
* of things, like check schema, support entries, write an audit trail, etc.
|
||||
* If such a plugin is "bundled", you can simply call `mount` with that plugin
|
||||
* and assign it a point in the DIT, to save you manually building up the
|
||||
* handler chains for all the ops.
|
||||
*
|
||||
* @param {String} name the point in the tree to mount at.
|
||||
* @param {Object} backend an LDAP Backend (See the docs).
|
||||
* @return {Server} this so you can chain.
|
||||
* @throws {TypeError} on bad input.
|
||||
*/
|
||||
Server.prototype.mount = function(name, backend) {
|
||||
if (!name || typeof(name) !== 'string')
|
||||
throw new TypeError('name (string) required');
|
||||
if (!backend || typeof(backend) !== 'object')
|
||||
throw new TypeError('backend (object) required');
|
||||
if (!backend.name || typeof(backend.name) !== 'string')
|
||||
throw new TypeError('backend is not a valid LDAP Backend');
|
||||
if (!backend.register || typeof(backend.register) !== 'function')
|
||||
throw new TypeError('backend is not a valid LDAP Backend');
|
||||
|
||||
var _dn = dn.parse(name).toString();
|
||||
name = _dn.toString();
|
||||
|
||||
var self = this;
|
||||
|
||||
// This is slightly ghetto, but easier than repeating all the code here.
|
||||
var ops = ['add', 'bind', 'compare', 'del', 'modify', 'modifyDN', 'search'];
|
||||
ops.forEach(function(o) {
|
||||
self[o](name, backend.register(o));
|
||||
});
|
||||
|
||||
// Overwrite the route table's backend with backend
|
||||
var route = this._getRoute(_dn);
|
||||
route.backend = backend;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
// All these just reexpose the requisite net.Server APIs
|
||||
Server.prototype.listen = function(port, host, callback) {
|
||||
if (typeof(host) === 'function')
|
||||
callback = host;
|
||||
|
||||
return this.server.listen(port, function() {
|
||||
if (typeof(callback) === 'function')
|
||||
return callback();
|
||||
});
|
||||
};
|
||||
Server.prototype.listenFD = function(fd) {
|
||||
return this.server.listenFD(fd);
|
||||
};
|
||||
Server.prototype.close = function() {
|
||||
return this.server.close();
|
||||
};
|
||||
Server.prototype.address = function() {
|
||||
return this.server.address();
|
||||
};
|
||||
|
||||
|
||||
Server.prototype._getRoute = function(_dn, backend) {
|
||||
assert.ok(dn);
|
||||
|
||||
if (!backend)
|
||||
backend = this;
|
||||
|
||||
var name;
|
||||
if (_dn instanceof dn.DN) {
|
||||
name = _dn.toString();
|
||||
} else {
|
||||
name = _dn;
|
||||
}
|
||||
|
||||
if (!this.routes[name]) {
|
||||
this.routes[name] = {};
|
||||
this.routes[name].backend = backend;
|
||||
this.routes[name].dn = _dn;
|
||||
}
|
||||
|
||||
return this.routes[name];
|
||||
};
|
||||
|
||||
|
||||
Server.prototype._getHandlerChain = function(req) {
|
||||
assert.ok(req);
|
||||
|
||||
var op = '0x' + req.protocolOp.toString(16);
|
||||
|
||||
var self = this;
|
||||
var routes = this.routes;
|
||||
for (var r in routes) {
|
||||
if (routes.hasOwnProperty(r)) {
|
||||
var route = routes[r];
|
||||
// Special cases are exops and unbinds, handle those first.
|
||||
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
|
||||
if (r !== req.requestName)
|
||||
continue;
|
||||
|
||||
return {
|
||||
backend: routes.backend,
|
||||
handlers: route[op] || [defaultExopHandler]
|
||||
};
|
||||
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
|
||||
return {
|
||||
backend: routes['unbind'].backend,
|
||||
handlers: routes['unbind'][op] || [defaultUnbindHandler]
|
||||
};
|
||||
}
|
||||
|
||||
if (!route[op])
|
||||
continue;
|
||||
|
||||
// Otherwise, match via DN rules
|
||||
assert.ok(req.dn);
|
||||
assert.ok(route.dn);
|
||||
if (r !== req.dn.toString() && (!route.dn.parentOf(req.dn)))
|
||||
continue;
|
||||
|
||||
// We should be good to go.
|
||||
return {
|
||||
backend: route.backend,
|
||||
handlers: route[op] || [defaultHandler]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// We're here, so nothing matched.
|
||||
return {
|
||||
backend: self,
|
||||
handlers: [(req.protocolOp !== Protocol.LDAP_REQ_EXTENSION ?
|
||||
noSuffixHandler : noExOpHandler)]
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -92,7 +92,6 @@ test('setup', function(t) {
|
|||
return next();
|
||||
});
|
||||
|
||||
|
||||
server.unbind(function(req, res, next) {
|
||||
res.end();
|
||||
return next();
|
||||
|
|
|
@ -44,7 +44,7 @@ test('new with args', function(t) {
|
|||
})]
|
||||
});
|
||||
t.ok(req);
|
||||
t.equal(req.dn, 'cn=foo, o=test');
|
||||
t.equal(req.dn.toString(), 'cn=foo, o=test');
|
||||
t.equal(req.changes.length, 1);
|
||||
t.equal(req.changes[0].operation, 'Replace');
|
||||
t.equal(req.changes[0].modification.type, 'objectclass');
|
||||
|
|
|
@ -41,7 +41,7 @@ test('new with args', function(t) {
|
|||
});
|
||||
t.ok(res);
|
||||
t.equal(res.messageID, 123);
|
||||
t.equal(res.dn, 'cn=foo, o=test');
|
||||
t.equal(res.dn.toString(), 'cn=foo, o=test');
|
||||
t.equal(res.attributes.length, 2);
|
||||
t.equal(res.attributes[0].type, 'cn');
|
||||
t.equal(res.attributes[0].vals[0], 'foo');
|
||||
|
|
Loading…
Reference in New Issue