From e92977b5148d6545f2a108f13915588282477818 Mon Sep 17 00:00:00 2001 From: Patrick Mooney Date: Thu, 5 Jun 2014 16:47:09 -0500 Subject: [PATCH] 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 --- lib/server.js | 112 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 34 deletions(-) diff --git a/lib/server.js b/lib/server.js index 89da85e..7190917 100644 --- a/lib/server.js +++ b/lib/server.js @@ -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 }; };