Refactor server route handling

Force route lookups to proceed lexically through mounted endpoint DNs.
Mounting to a null ('') DN will act as the default route for requests
which aren't matched by defined routes.

Fix mcavage/node-ldapjs#154
Fix mcavage/node-ldapjs#111
This commit is contained in:
Patrick Mooney 2014-06-05 16:47:09 -05:00
parent 0427732c10
commit e92977b514
1 changed files with 78 additions and 34 deletions

View File

@ -716,12 +716,45 @@ Server.prototype._getRoute = function (_dn, backend) {
this.routes[name] = {};
this.routes[name].backend = backend;
this.routes[name].dn = _dn;
// Force regeneration of the route key cache on next request
this._routeKeyCache = null;
}
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) {
assert.ok(req);
@ -738,59 +771,70 @@ Server.prototype._getHandlerChain = function _getHandlerChain(req, res) {
}
var op = '0x' + req.protocolOp.toString(16);
var self = this;
var routes = this.routes;
var keys = Object.keys(routes);
for (var i = 0; i < keys.length; i++) {
var r = keys[i];
var route = routes[r];
var route;
// Special cases are abandons, exops and unbinds, handle those first.
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
if (r === req.requestName) {
return {
backend: routes.backend,
handlers: route[op] || [noExOpHandler]
};
}
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
// Special cases are exops, unbinds and abandons. Handle those first.
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
route = routes[req.requestName];
if (route) {
return {
backend: routes['unbind'] ? routes['unbind'].backend : self,
handlers: function getUnbindChain() {
if (routes['unbind'] && routes['unbind'][op])
return routes['unbind'][op];
self.log.debug('%s unbind request %j', req.logId, req.json);
return [defaultNoOpHandler];
}
backend: route.backend,
handlers: (route[op] ? route[op] : [noExOpHandler])
};
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) {
} else {
return {
backend: self,
handlers: [defaultNoOpHandler]
handlers: [noExOpHandler]
};
} else if (route[op]) {
// Otherwise, match via DN rules
assert.ok(req.dn);
assert.ok(route.dn);
}
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
route = routes['unbind'];
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.
req.suffix = route.dn;
return {
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 {
backend: self,
handlers: [(req.protocolOp !== Protocol.LDAP_REQ_EXTENSION ?
noSuffixHandler : noExOpHandler)]
handlers: fallbackHandler
};
};