Merge pull request #195 from pfmooney/route-pr
Server request routing update
This commit is contained in:
commit
d6780e30cb
112
lib/server.js
112
lib/server.js
|
@ -716,12 +716,45 @@ Server.prototype._getRoute = function (_dn, backend) {
|
||||||
this.routes[name] = {};
|
this.routes[name] = {};
|
||||||
this.routes[name].backend = backend;
|
this.routes[name].backend = backend;
|
||||||
this.routes[name].dn = _dn;
|
this.routes[name].dn = _dn;
|
||||||
|
// Force regeneration of the route key cache on next request
|
||||||
|
this._routeKeyCache = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.routes[name];
|
return this.routes[name];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Server.prototype._sortedRouteKeys = function _sortedRouteKeys() {
|
||||||
|
// The filtered/sorted route keys are cached to prevent needlessly
|
||||||
|
// regenerating the list for every incoming request.
|
||||||
|
if (!this._routeKeyCache) {
|
||||||
|
var self = this;
|
||||||
|
var reversedRDNsToKeys = {};
|
||||||
|
// Generate mapping of reversedRDNs(DN) -> routeKey
|
||||||
|
Object.keys(this.routes).forEach(function (key) {
|
||||||
|
var _dn = self.routes[key].dn;
|
||||||
|
// Ignore non-DN routes such as exop or unbind
|
||||||
|
if (_dn instanceof dn.DN) {
|
||||||
|
var reversed = _dn.clone();
|
||||||
|
reversed.rdns.reverse();
|
||||||
|
reversedRDNsToKeys[reversed.spaced(true).toString()] = key;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var output = [];
|
||||||
|
// Reverse-sort on reversedRDS(DN) in order to output routeKey list.
|
||||||
|
// This will place more specific DNs in front of their parents:
|
||||||
|
// 1. dc=test, dc=domain, dc=sub
|
||||||
|
// 2. dc=test, dc=domain
|
||||||
|
// 3. dc=other, dc=foobar
|
||||||
|
Object.keys(reversedRDNsToKeys).sort().reverse().forEach(function (_dn) {
|
||||||
|
output.push(reversedRDNsToKeys[_dn]);
|
||||||
|
});
|
||||||
|
this._routeKeyCache = output;
|
||||||
|
}
|
||||||
|
return this._routeKeyCache;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
Server.prototype._getHandlerChain = function _getHandlerChain(req, res) {
|
Server.prototype._getHandlerChain = function _getHandlerChain(req, res) {
|
||||||
assert.ok(req);
|
assert.ok(req);
|
||||||
|
|
||||||
|
@ -738,59 +771,70 @@ Server.prototype._getHandlerChain = function _getHandlerChain(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var op = '0x' + req.protocolOp.toString(16);
|
var op = '0x' + req.protocolOp.toString(16);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var routes = this.routes;
|
var routes = this.routes;
|
||||||
var keys = Object.keys(routes);
|
var route;
|
||||||
for (var i = 0; i < keys.length; i++) {
|
|
||||||
var r = keys[i];
|
|
||||||
var route = routes[r];
|
|
||||||
|
|
||||||
// Special cases are abandons, exops and unbinds, handle those first.
|
// Special cases are exops, unbinds and abandons. Handle those first.
|
||||||
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
|
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
|
||||||
if (r === req.requestName) {
|
route = routes[req.requestName];
|
||||||
return {
|
if (route) {
|
||||||
backend: routes.backend,
|
|
||||||
handlers: route[op] || [noExOpHandler]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
|
|
||||||
return {
|
return {
|
||||||
backend: routes['unbind'] ? routes['unbind'].backend : self,
|
backend: route.backend,
|
||||||
handlers: function getUnbindChain() {
|
handlers: (route[op] ? route[op] : [noExOpHandler])
|
||||||
if (routes['unbind'] && routes['unbind'][op])
|
|
||||||
return routes['unbind'][op];
|
|
||||||
|
|
||||||
self.log.debug('%s unbind request %j', req.logId, req.json);
|
|
||||||
return [defaultNoOpHandler];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) {
|
} else {
|
||||||
return {
|
return {
|
||||||
backend: self,
|
backend: self,
|
||||||
handlers: [defaultNoOpHandler]
|
handlers: [noExOpHandler]
|
||||||
};
|
};
|
||||||
} else if (route[op]) {
|
}
|
||||||
// Otherwise, match via DN rules
|
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
|
||||||
assert.ok(req.dn);
|
route = routes['unbind'];
|
||||||
assert.ok(route.dn);
|
return {
|
||||||
|
backend: route ? route.backend : self,
|
||||||
|
handlers: function getUnbindChain() {
|
||||||
|
if (route && route[op])
|
||||||
|
return route[op];
|
||||||
|
|
||||||
if (route.dn.equals(req.dn) || route.dn.parentOf(req.dn)) {
|
self.log.debug('%s unbind request %j', req.logId, req.json);
|
||||||
|
return [defaultNoOpHandler];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) {
|
||||||
|
return {
|
||||||
|
backend: self,
|
||||||
|
handlers: [defaultNoOpHandler]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, match via DN rules
|
||||||
|
assert.ok(req.dn);
|
||||||
|
var keys = this._sortedRouteKeys();
|
||||||
|
var fallbackHandler = [noSuffixHandler];
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var suffix = keys[i];
|
||||||
|
route = routes[suffix];
|
||||||
|
assert.ok(route.dn);
|
||||||
|
// Match a valid route or the route wildcard ('')
|
||||||
|
if (route.dn.equals(req.dn) || route.dn.parentOf(req.dn) || suffix === '') {
|
||||||
|
if (route[op]) {
|
||||||
// We should be good to go.
|
// We should be good to go.
|
||||||
req.suffix = route.dn;
|
req.suffix = route.dn;
|
||||||
return {
|
return {
|
||||||
backend: route.backend,
|
backend: route.backend,
|
||||||
handlers: route[op] || [defaultHandler]
|
handlers: route[op]
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
// We found a valid suffix but not a valid operation.
|
||||||
|
// There might be a more generic suffix with a legitimate operation.
|
||||||
|
fallbackHandler = [defaultHandler];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're here, so nothing matched.
|
|
||||||
return {
|
return {
|
||||||
backend: self,
|
backend: self,
|
||||||
handlers: [(req.protocolOp !== Protocol.LDAP_REQ_EXTENSION ?
|
handlers: fallbackHandler
|
||||||
noSuffixHandler : noExOpHandler)]
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,8 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tap": "0.4.1",
|
"tap": "0.4.1",
|
||||||
"node-uuid": "1.4.0"
|
"node-uuid": "1.4.0",
|
||||||
|
"vasync": "1.4.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "./node_modules/.bin/tap ./test"
|
"test": "./node_modules/.bin/tap ./test"
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||||
|
|
||||||
|
var Logger = require('bunyan');
|
||||||
|
|
||||||
|
var test = require('tap').test;
|
||||||
|
var uuid = require('node-uuid');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
|
||||||
|
///--- Globals
|
||||||
|
|
||||||
|
var BIND_DN = 'cn=root';
|
||||||
|
var BIND_PW = 'secret';
|
||||||
|
|
||||||
|
var SUFFIX = 'dc=test';
|
||||||
|
|
||||||
|
var ldap;
|
||||||
|
var Attribute;
|
||||||
|
var Change;
|
||||||
|
var client;
|
||||||
|
var server;
|
||||||
|
var sock;
|
||||||
|
|
||||||
|
function getSock() {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
return '\\\\.\\pipe\\' + uuid();
|
||||||
|
} else {
|
||||||
|
return '/tmp/.' + uuid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///--- Tests
|
||||||
|
|
||||||
|
test('load library', function (t) {
|
||||||
|
ldap = require('../lib/index');
|
||||||
|
t.ok(ldap.createServer);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic create', function (t) {
|
||||||
|
server = ldap.createServer();
|
||||||
|
t.ok(server);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('listen on unix/named socket', { timeout: 1000 }, function (t) {
|
||||||
|
t.plan(1);
|
||||||
|
server = ldap.createServer();
|
||||||
|
sock = getSock();
|
||||||
|
server.listen(sock, function () {
|
||||||
|
t.ok(true);
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('listen on ephemeral port', { timeout: 1000 }, function (t) {
|
||||||
|
t.plan(2);
|
||||||
|
server = ldap.createServer();
|
||||||
|
server.listen(0, 'localhost', function () {
|
||||||
|
var addr = server.address();
|
||||||
|
t.ok(addr.port > 0);
|
||||||
|
t.ok(addr.port < 65535);
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('route order', function (t) {
|
||||||
|
function generateHandler(response) {
|
||||||
|
var func = function handler(req, res, next) {
|
||||||
|
res.send({
|
||||||
|
dn: response,
|
||||||
|
attributes: { }
|
||||||
|
});
|
||||||
|
res.end();
|
||||||
|
return next();
|
||||||
|
};
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
|
||||||
|
server = ldap.createServer();
|
||||||
|
sock = getSock();
|
||||||
|
var dnShort = SUFFIX;
|
||||||
|
var dnMed = 'dc=sub, ' + SUFFIX;
|
||||||
|
var dnLong = 'dc=long, dc=sub, ' + SUFFIX;
|
||||||
|
|
||||||
|
// Mount routes out of order
|
||||||
|
server.search(dnMed, generateHandler(dnMed));
|
||||||
|
server.search(dnShort, generateHandler(dnShort));
|
||||||
|
server.search(dnLong, generateHandler(dnLong));
|
||||||
|
server.listen(sock, function () {
|
||||||
|
t.ok(true, 'server listen');
|
||||||
|
client = ldap.createClient({ socketPath: sock });
|
||||||
|
function runSearch(value, cb) {
|
||||||
|
client.search(value, '(objectclass=*)', function (err, res) {
|
||||||
|
t.ifError(err);
|
||||||
|
t.ok(res);
|
||||||
|
res.on('searchEntry', function (entry) {
|
||||||
|
t.equal(entry.dn.toString(), value);
|
||||||
|
});
|
||||||
|
res.on('end', function () {
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
vasync.forEachParallel({
|
||||||
|
'func': runSearch,
|
||||||
|
'inputs': [dnShort, dnMed, dnLong]
|
||||||
|
}, function (err, results) {
|
||||||
|
t.notOk(err);
|
||||||
|
client.unbind();
|
||||||
|
server.close();
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('route absent', function (t) {
|
||||||
|
server = ldap.createServer();
|
||||||
|
sock = getSock();
|
||||||
|
var DN_ROUTE = 'dc=base';
|
||||||
|
var DN_MISSING = 'dc=absent';
|
||||||
|
|
||||||
|
server.bind(DN_ROUTE, function (req, res, next) {
|
||||||
|
res.end();
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(sock, function () {
|
||||||
|
t.ok(true, 'server startup');
|
||||||
|
vasync.parallel({
|
||||||
|
'funcs': [
|
||||||
|
function presentBind(cb) {
|
||||||
|
var clt = ldap.createClient({ socketPath: sock });
|
||||||
|
clt.bind(DN_ROUTE, '', function (err) {
|
||||||
|
t.notOk(err);
|
||||||
|
clt.unbind();
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function absentBind(cb) {
|
||||||
|
var clt = ldap.createClient({ socketPath: sock });
|
||||||
|
clt.bind(DN_MISSING, '', function (err) {
|
||||||
|
t.ok(err);
|
||||||
|
t.equal(err.code, ldap.LDAP_NO_SUCH_OBJECT);
|
||||||
|
clt.unbind();
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, function (err, result) {
|
||||||
|
t.notOk(err);
|
||||||
|
server.close();
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue