Support invalid DNs in client/server

Some LDAP implementations (mainly AD and Outlook) accept and/or output
DNs that are not valid.  To support interaction with these invalid DNs a
strictDN flag (default: true) has been added to the client and server
constructors.  Setting this flag to false will allow use of
non-conforming DNs.

When disabling strictDN in the ldapjs client, strings which wouldn't
parse into a DN can then be passed to the ldap operation methods.  It
also means that some methods (such as search) may return results with
string-formatted DNs instead of DN objects.

When disabling strictDN in the ldapjs server, incoming requests that
contain invalid DNs will be routed to the default ('') handler for that
operation type.  It is your responsiblity to differentiate between
string-type and object-type DNs in those handlers.

Fix mcavage/node-ldapjs#222
Fix mcavage/node-ldapjs#146
Fix mcavage/node-ldapjs#113
Fix mcavage/node-ldapjs#104
This commit is contained in:
Patrick Mooney 2014-09-30 18:39:19 -05:00
parent 58f58883cd
commit 408e7c9f99
18 changed files with 211 additions and 109 deletions

View File

@ -80,6 +80,18 @@ function validateControls(controls) {
return controls; return controls;
} }
function ensureDN(input, strict) {
if (dn.DN.isDN(input)) {
return dn;
} else if (strict) {
return dn.parse(input);
} else if (typeof (input) === 'string') {
return input;
} else {
throw new Error('invalid DN');
}
}
/** /**
* Queue to contain LDAP requests. * Queue to contain LDAP requests.
* *
@ -318,6 +330,7 @@ function Client(options) {
failAfter: parseInt(rOpts.failAfter, 10) || Infinity failAfter: parseInt(rOpts.failAfter, 10) || Infinity
}; };
} }
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true;
this.queue = new RequestQueue({ this.queue = new RequestQueue({
size: parseInt((options.queueSize || 0), 10), size: parseInt((options.queueSize || 0), 10),
@ -394,7 +407,7 @@ Client.prototype.abandon = function abandon(messageID, controls, callback) {
* @throws {TypeError} on invalid input. * @throws {TypeError} on invalid input.
*/ */
Client.prototype.add = function add(name, entry, controls, callback) { Client.prototype.add = function add(name, entry, controls, callback) {
assert.string(name, 'name'); assert.ok(name, 'name');
assert.object(entry, 'entry'); assert.object(entry, 'entry');
if (typeof (controls) === 'function') { if (typeof (controls) === 'function') {
callback = controls; callback = controls;
@ -427,7 +440,7 @@ Client.prototype.add = function add(name, entry, controls, callback) {
} }
var req = new AddRequest({ var req = new AddRequest({
entry: dn.parse(name), entry: ensureDN(name, this.strictDN),
attributes: entry, attributes: entry,
controls: controls controls: controls
}); });
@ -487,7 +500,7 @@ Client.prototype.compare = function compare(name,
value, value,
controls, controls,
callback) { callback) {
assert.string(name, 'name'); assert.ok(name, 'name');
assert.string(attr, 'attr'); assert.string(attr, 'attr');
assert.string(value, 'value'); assert.string(value, 'value');
if (typeof (controls) === 'function') { if (typeof (controls) === 'function') {
@ -499,7 +512,7 @@ Client.prototype.compare = function compare(name,
assert.func(callback, 'callback'); assert.func(callback, 'callback');
var req = new CompareRequest({ var req = new CompareRequest({
entry: dn.parse(name), entry: ensureDN(name, this.strictDN),
attribute: attr, attribute: attr,
value: value, value: value,
controls: controls controls: controls
@ -523,7 +536,7 @@ Client.prototype.compare = function compare(name,
* @throws {TypeError} on invalid input. * @throws {TypeError} on invalid input.
*/ */
Client.prototype.del = function del(name, controls, callback) { Client.prototype.del = function del(name, controls, callback) {
assert.string(name, 'name'); assert.ok(name, 'name');
if (typeof (controls) === 'function') { if (typeof (controls) === 'function') {
callback = controls; callback = controls;
controls = []; controls = [];
@ -533,7 +546,7 @@ Client.prototype.del = function del(name, controls, callback) {
assert.func(callback, 'callback'); assert.func(callback, 'callback');
var req = new DeleteRequest({ var req = new DeleteRequest({
entry: dn.parse(name), entry: ensureDN(name, this.strictDN),
controls: controls controls: controls
}); });
@ -596,7 +609,7 @@ Client.prototype.exop = function exop(name, value, controls, callback) {
* @throws {TypeError} on invalid input. * @throws {TypeError} on invalid input.
*/ */
Client.prototype.modify = function modify(name, change, controls, callback) { Client.prototype.modify = function modify(name, change, controls, callback) {
assert.string(name, 'name'); assert.ok(name, 'name');
assert.object(change, 'change'); assert.object(change, 'change');
var changes = []; var changes = [];
@ -651,7 +664,7 @@ Client.prototype.modify = function modify(name, change, controls, callback) {
assert.func(callback, 'callback'); assert.func(callback, 'callback');
var req = new ModifyRequest({ var req = new ModifyRequest({
object: dn.parse(name), object: ensureDN(name, this.strictDN),
changes: changes, changes: changes,
controls: controls controls: controls
}); });
@ -678,20 +691,18 @@ Client.prototype.modifyDN = function modifyDN(name,
newName, newName,
controls, controls,
callback) { callback) {
if (typeof (name) !== 'string') assert.ok(name, 'name');
throw new TypeError('name (string) required'); assert.string(newName, 'newName');
if (typeof (newName) !== 'string')
throw new TypeError('newName (string) required');
if (typeof (controls) === 'function') { if (typeof (controls) === 'function') {
callback = controls; callback = controls;
controls = []; controls = [];
} else { } else {
controls = validateControls(controls); controls = validateControls(controls);
} }
if (typeof (callback) !== 'function') assert.func(callback);
throw new TypeError('callback (function) required');
var DN = dn.parse(name); var DN = ensureDN(name);
// TODO: is non-strict handling desired here?
var newDN = dn.parse(newName); var newDN = dn.parse(newName);
var req = new ModifyDNRequest({ var req = new ModifyDNRequest({
@ -739,8 +750,7 @@ Client.prototype.search = function search(base,
controls, controls,
callback, callback,
_bypass) { _bypass) {
if (typeof (base) !== 'string' && !(base instanceof dn.DN)) assert.ok(base, 'base');
throw new TypeError('base (string) required');
if (Array.isArray(options) || (options instanceof Control)) { if (Array.isArray(options) || (options instanceof Control)) {
controls = options; controls = options;
options = {}; options = {};
@ -762,15 +772,13 @@ Client.prototype.search = function search(base,
} else if (!filters.isFilter(options.filter)) { } else if (!filters.isFilter(options.filter)) {
throw new TypeError('options.filter (Filter) required'); throw new TypeError('options.filter (Filter) required');
} }
if (typeof (controls) === 'function') { if (typeof (controls) === 'function') {
callback = controls; callback = controls;
controls = []; controls = [];
} else { } else {
controls = validateControls(controls); controls = validateControls(controls);
} }
if (typeof (callback) !== 'function') assert.func(callback, 'callback');
throw new TypeError('callback (function) required');
if (options.attributes) { if (options.attributes) {
if (!Array.isArray(options.attributes)) { if (!Array.isArray(options.attributes)) {
@ -783,9 +791,10 @@ Client.prototype.search = function search(base,
} }
var self = this; var self = this;
var baseDN = ensureDN(base, this.strictDN);
function sendRequest(ctrls, emitter, cb) { function sendRequest(ctrls, emitter, cb) {
var req = new SearchRequest({ var req = new SearchRequest({
baseObject: typeof (base) === 'string' ? dn.parse(base) : base, baseObject: baseDN,
scope: options.scope || 'base', scope: options.scope || 'base',
filter: options.filter, filter: options.filter,
derefAliases: options.derefAliases || Protocol.NEVER_DEREF_ALIASES, derefAliases: options.derefAliases || Protocol.NEVER_DEREF_ALIASES,

View File

@ -218,6 +218,7 @@ DN.prototype.toString = function () {
return _dn.join(this.rdnSpaced ? ', ' : ','); return _dn.join(this.rdnSpaced ? ', ' : ',');
}; };
DN.prototype.spaced = function (spaces) { DN.prototype.spaced = function (spaces) {
this.rdnSpaced = (spaces === false) ? false : true; this.rdnSpaced = (spaces === false) ? false : true;
return this; return this;
@ -357,6 +358,20 @@ DN.prototype.unshift = function (rdn) {
}; };
DN.isDN = function (dn) {
if (!dn || typeof (dn) !== 'object') {
return false;
}
if (dn instanceof DN) {
return true;
}
if (Array.isArray(dn.rdns)) {
// Really simple duck-typing for now
return true;
}
return false;
};
///--- Exports ///--- Exports

View File

@ -3,19 +3,9 @@
var assert = require('assert'); var assert = require('assert');
var util = require('util'); var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message'); var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Attribute = require('../attribute');
var Protocol = require('../protocol'); var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API ///--- API

View File

@ -3,19 +3,16 @@
var assert = require('assert'); var assert = require('assert');
var util = require('util'); var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message'); var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn'); var dn = require('../dn');
var Attribute = require('../attribute'); var Attribute = require('../attribute');
var Protocol = require('../protocol'); var Protocol = require('../protocol');
///--- Globals ///--- Globals
var Ber = asn1.Ber; var isDN = dn.DN.isDN;
///--- API ///--- API
@ -24,8 +21,10 @@ function AddRequest(options) {
if (options) { if (options) {
if (typeof (options) !== 'object') if (typeof (options) !== 'object')
throw new TypeError('options must be an object'); throw new TypeError('options must be an object');
if (options.entry && !(options.entry instanceof dn.DN)) if (options.entry &&
throw new TypeError('options.entry must be a DN'); !(isDN(options.entry) || typeof (options.entry) === 'string')) {
throw new TypeError('options.entry must be a DN or string');
}
if (options.attributes) { if (options.attributes) {
if (!Array.isArray(options.attributes)) if (!Array.isArray(options.attributes))
throw new TypeError('options.attributes must be [Attribute]'); throw new TypeError('options.attributes must be [Attribute]');
@ -55,7 +54,7 @@ module.exports = AddRequest;
AddRequest.prototype._parse = function (ber) { AddRequest.prototype._parse = function (ber) {
assert.ok(ber); assert.ok(ber);
this.entry = dn.parse(ber.readString()); this.entry = ber.readString();
ber.readSequence(); ber.readSequence();

View File

@ -6,16 +6,11 @@ var util = require('util');
var asn1 = require('asn1'); var asn1 = require('asn1');
var LDAPMessage = require('./message'); var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Protocol = require('../protocol'); var Protocol = require('../protocol');
///--- Globals ///--- Globals
var Ber = asn1.Ber; var Ber = asn1.Ber;
var LDAP_BIND_SIMPLE = 'simple'; var LDAP_BIND_SIMPLE = 'simple';
var LDAP_BIND_SASL = 'sasl'; var LDAP_BIND_SASL = 'sasl';
@ -49,7 +44,7 @@ BindRequest.prototype._parse = function (ber) {
assert.ok(ber); assert.ok(ber);
this.version = ber.readInt(); this.version = ber.readInt();
this.name = dn.parse(ber.readString()); this.name = ber.readString();
var t = ber.peek(); var t = ber.peek();

View File

@ -4,21 +4,26 @@ var assert = require('assert');
var util = require('util'); var util = require('util');
var LDAPMessage = require('./message'); var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn'); var dn = require('../dn');
var Protocol = require('../protocol'); var Protocol = require('../protocol');
///--- Globals
var isDN = dn.DN.isDN;
///--- API ///--- API
function CompareRequest(options) { function CompareRequest(options) {
if (options) { if (options) {
if (typeof (options) !== 'object') if (typeof (options) !== 'object')
throw new TypeError('options must be an object'); throw new TypeError('options must be an object');
if (options.entry && !(options.entry instanceof dn.DN)) if (options.entry &&
throw new TypeError('options.entry must be a DN'); !(isDN(options.entry) || typeof (options.entry) === 'string')) {
throw new TypeError('options.entry must be a DN or string');
}
if (options.attribute && typeof (options.attribute) !== 'string') if (options.attribute && typeof (options.attribute) !== 'string')
throw new TypeError('options.attribute must be a string'); throw new TypeError('options.attribute must be a string');
if (options.value && typeof (options.value) !== 'string') if (options.value && typeof (options.value) !== 'string')
@ -36,9 +41,7 @@ function CompareRequest(options) {
var self = this; var self = this;
this.__defineGetter__('type', function () { return 'CompareRequest'; }); this.__defineGetter__('type', function () { return 'CompareRequest'; });
this.__defineGetter__('_dn', function () { this.__defineGetter__('_dn', function () { return self.entry; });
return self.entry ? self.entry.toString() : '';
});
} }
util.inherits(CompareRequest, LDAPMessage); util.inherits(CompareRequest, LDAPMessage);
module.exports = CompareRequest; module.exports = CompareRequest;
@ -47,7 +50,7 @@ module.exports = CompareRequest;
CompareRequest.prototype._parse = function (ber) { CompareRequest.prototype._parse = function (ber) {
assert.ok(ber); assert.ok(ber);
this.entry = dn.parse(ber.readString()); this.entry = ber.readString();
ber.readSequence(); ber.readSequence();
this.attribute = ber.readString().toLowerCase(); this.attribute = ber.readString().toLowerCase();

View File

@ -3,19 +3,15 @@
var assert = require('assert'); var assert = require('assert');
var util = require('util'); var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message'); var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn'); var dn = require('../dn');
var Attribute = require('../attribute');
var Protocol = require('../protocol'); var Protocol = require('../protocol');
///--- Globals ///--- Globals
var Ber = asn1.Ber; var isDN = dn.DN.isDN;
///--- API ///--- API
@ -24,8 +20,10 @@ function DeleteRequest(options) {
if (options) { if (options) {
if (typeof (options) !== 'object') if (typeof (options) !== 'object')
throw new TypeError('options must be an object'); throw new TypeError('options must be an object');
if (options.entry && !(options.entry instanceof dn.DN)) if (options.entry &&
throw new TypeError('options.entry must be a DN'); !(isDN(options.entry) || typeof (options.entry) === 'string')) {
throw new TypeError('options.entry must be a DN or string');
}
} else { } else {
options = {}; options = {};
} }
@ -46,7 +44,7 @@ module.exports = DeleteRequest;
DeleteRequest.prototype._parse = function (ber, length) { DeleteRequest.prototype._parse = function (ber, length) {
assert.ok(ber); assert.ok(ber);
this.entry = dn.parse(ber.buffer.slice(0, length).toString('utf8')); this.entry = ber.buffer.slice(0, length).toString('utf8');
ber._offset += ber.length; ber._offset += ber.length;
return true; return true;

View File

@ -3,22 +3,11 @@
var assert = require('assert'); var assert = require('assert');
var util = require('util'); var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message'); var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Protocol = require('../protocol'); var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API ///--- API
function ExtendedRequest(options) { function ExtendedRequest(options) {

View File

@ -3,19 +3,14 @@
var assert = require('assert'); var assert = require('assert');
var util = require('util'); var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message'); var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Protocol = require('../protocol'); var Protocol = require('../protocol');
var dn = require('../dn');
///--- Globals ///--- Globals
var Ber = asn1.Ber; var isDN = dn.DN.isDN;
///--- API ///--- API
@ -24,14 +19,16 @@ function ModifyDNRequest(options) {
if (options) { if (options) {
if (typeof (options) !== 'object') if (typeof (options) !== 'object')
throw new TypeError('options must be an object'); throw new TypeError('options must be an object');
if (options.entry && !(options.entry instanceof dn.DN)) if (options.entry &&
throw new TypeError('options.entry must be a DN'); !(isDN(options.entry) || typeof (options.entry) === 'string')) {
if (options.newRdn && !(options.newRdn instanceof dn.DN)) throw new TypeError('options.entry must be a DN or string');
}
if (options.newRdn && !isDN(options.newRdn))
throw new TypeError('options.newRdn must be a DN'); throw new TypeError('options.newRdn must be a DN');
if (options.deleteOldRdn !== undefined && if (options.deleteOldRdn !== undefined &&
typeof (options.deleteOldRdn) !== 'boolean') typeof (options.deleteOldRdn) !== 'boolean')
throw new TypeError('options.deleteOldRdn must be a boolean'); throw new TypeError('options.deleteOldRdn must be a boolean');
if (options.newSuperior && !(options.newSuperior instanceof dn.DN)) if (options.newSuperior && !isDN(options.newSuperior))
throw new TypeError('options.newSuperior must be a DN'); throw new TypeError('options.newSuperior must be a DN');
} else { } else {
@ -57,7 +54,7 @@ module.exports = ModifyDNRequest;
ModifyDNRequest.prototype._parse = function (ber) { ModifyDNRequest.prototype._parse = function (ber) {
assert.ok(ber); assert.ok(ber);
this.entry = dn.parse(ber.readString()); this.entry = ber.readString();
this.newRdn = dn.parse(ber.readString()); this.newRdn = dn.parse(ber.readString());
this.deleteOldRdn = ber.readBoolean(); this.deleteOldRdn = ber.readBoolean();
if (ber.peek() === 0x80) if (ber.peek() === 0x80)

View File

@ -4,8 +4,6 @@ var assert = require('assert');
var util = require('util'); var util = require('util');
var LDAPMessage = require('./message'); var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn'); var dn = require('../dn');
var Attribute = require('../attribute'); var Attribute = require('../attribute');
var Change = require('../change'); var Change = require('../change');
@ -13,14 +11,21 @@ var Protocol = require('../protocol');
///--- API
var isDN = dn.DN.isDN;
///--- API ///--- API
function ModifyRequest(options) { function ModifyRequest(options) {
if (options) { if (options) {
if (typeof (options) !== 'object') if (typeof (options) !== 'object')
throw new TypeError('options must be an object'); throw new TypeError('options must be an object');
if (options.object && !(options.object instanceof dn.DN)) if (options.object &&
throw new TypeError('options.object must be a DN'); !(isDN(options.object) || typeof (options.object) === 'string')) {
throw new TypeError('options.object must be a DN or string');
}
if (options.attributes) { if (options.attributes) {
if (!Array.isArray(options.attributes)) if (!Array.isArray(options.attributes))
throw new TypeError('options.attributes must be [Attribute]'); throw new TypeError('options.attributes must be [Attribute]');
@ -50,7 +55,7 @@ module.exports = ModifyRequest;
ModifyRequest.prototype._parse = function (ber) { ModifyRequest.prototype._parse = function (ber) {
assert.ok(ber); assert.ok(ber);
this.object = dn.parse(ber.readString()); this.object = ber.readString();
ber.readSequence(); ber.readSequence();
var end = ber.offset + ber.length; var end = ber.offset + ber.length;

View File

@ -0,0 +1,19 @@
// Copyright 2014 Joyent, Inc. All rights reserved.
function NoOpResponse(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 'NoOpResponse'; });
}
util.inherits(NoOpResponse, LDAPMessage);
module.exports = NoOpResponse;
NoOpResponse.prototype.end = function () {};
NoOpResponse.prototype._json = function (j) { return j; };

View File

@ -7,7 +7,6 @@ var asn1 = require('asn1');
var LDAPMessage = require('./message'); var LDAPMessage = require('./message');
var LDAPResult = require('./result'); var LDAPResult = require('./result');
var dn = require('../dn'); var dn = require('../dn');
var filters = require('../filters'); var filters = require('../filters');
var Protocol = require('../protocol'); var Protocol = require('../protocol');
@ -16,10 +15,10 @@ var Protocol = require('../protocol');
///--- Globals ///--- Globals
var DN = dn.DN;
var Ber = asn1.Ber; var Ber = asn1.Ber;
///--- API ///--- API
function SearchRequest(options) { function SearchRequest(options) {
@ -35,9 +34,7 @@ function SearchRequest(options) {
var self = this; var self = this;
this.__defineGetter__('type', function () { return 'SearchRequest'; }); this.__defineGetter__('type', function () { return 'SearchRequest'; });
this.__defineGetter__('_dn', function () { this.__defineGetter__('_dn', function () { return self.baseObject; });
return self.baseObject;
});
this.__defineGetter__('scope', function () { this.__defineGetter__('scope', function () {
switch (self._scope) { switch (self._scope) {
case Protocol.SCOPE_BASE_OBJECT: return 'base'; case Protocol.SCOPE_BASE_OBJECT: return 'base';
@ -67,7 +64,7 @@ function SearchRequest(options) {
} }
}); });
this.baseObject = options.baseObject || new dn.DN([ {} ]); this.baseObject = options.baseObject || new DN([ {} ]);
this.scope = options.scope || 'base'; this.scope = options.scope || 'base';
this.derefAliases = options.derefAliases || Protocol.NEVER_DEREF_ALIASES; this.derefAliases = options.derefAliases || Protocol.NEVER_DEREF_ALIASES;
this.sizeLimit = options.sizeLimit || 0; this.sizeLimit = options.sizeLimit || 0;
@ -93,7 +90,7 @@ SearchRequest.prototype.newResult = function () {
SearchRequest.prototype._parse = function (ber) { SearchRequest.prototype._parse = function (ber) {
assert.ok(ber); assert.ok(ber);
this.baseObject = dn.parse(ber.readString()); this.baseObject = ber.readString();
this.scope = ber.readEnumeration(); this.scope = ber.readEnumeration();
this.derefAliases = ber.readEnumeration(); this.derefAliases = ber.readEnumeration();
this.sizeLimit = ber.readInt(); this.sizeLimit = ber.readInt();

View File

@ -3,11 +3,7 @@
var assert = require('assert'); var assert = require('assert');
var util = require('util'); var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message'); var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn'); var dn = require('../dn');
var Protocol = require('../protocol'); var Protocol = require('../protocol');
@ -15,8 +11,6 @@ var Protocol = require('../protocol');
///--- Globals ///--- Globals
var Ber = asn1.Ber;
var DN = dn.DN; var DN = dn.DN;
var RDN = dn.RDN; var RDN = dn.RDN;

View File

@ -124,6 +124,45 @@ function getResponse(req) {
} }
function decodeDN(req, strict) {
assert.ok(req);
var parse;
if (strict) {
parse = dn.parse;
} else {
parse = function (input) {
try {
return dn.parse(input);
} catch (e) {
return input;
}
};
}
switch (req.protocolOp) {
case Protocol.LDAP_REQ_BIND:
req.name = parse(req.name);
break;
case Protocol.LDAP_REQ_ADD:
case Protocol.LDAP_REQ_COMPARE:
case Protocol.LDAP_REQ_DELETE:
req.entry = parse(req.entry);
break;
case Protocol.LDAP_REQ_MODIFY:
req.object = parse(req.object);
break;
case Protocol.LDAP_REQ_MODRDN:
req.entry = parse(req.entry);
// TODO: handle newRdn/Superior
break;
case Protocol.LDAP_REQ_SEARCH:
req.baseObject = parse(req.baseObject);
break;
default:
break;
}
}
function defaultHandler(req, res, next) { function defaultHandler(req, res, next) {
assert.ok(req); assert.ok(req);
assert.ok(res); assert.ok(res);
@ -276,6 +315,7 @@ function Server(options) {
this._chain = []; this._chain = [];
this.log = options.log; this.log = options.log;
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true;
var log = this.log; var log = this.log;
@ -355,6 +395,9 @@ function Server(options) {
return false; return false;
} }
// parse string DNs for routing/etc
decodeDN(req, this.strictDN);
res.connection = c; res.connection = c;
res.logId = req.logId; res.logId = req.logId;
res.requestDN = req.dn; res.requestDN = req.dn;
@ -808,12 +851,15 @@ Server.prototype._getHandlerChain = function _getHandlerChain(req, res) {
assert.ok(req.dn); assert.ok(req.dn);
var keys = this._sortedRouteKeys(); var keys = this._sortedRouteKeys();
var fallbackHandler = [noSuffixHandler]; var fallbackHandler = [noSuffixHandler];
// invalid DNs in non-strict mode are routed to the default handler
var testDN = (typeof (req.dn) === 'string') ? '' : req.dn;
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
var suffix = keys[i]; var suffix = keys[i];
route = routes[suffix]; route = routes[suffix];
assert.ok(route.dn); assert.ok(route.dn);
// Match a valid route or the route wildcard ('') // Match a valid route or the route wildcard ('')
if (route.dn.equals(req.dn) || route.dn.parentOf(req.dn) || suffix === '') { if (route.dn.equals(testDN) || route.dn.parentOf(testDN) || suffix === '') {
if (route[op]) { if (route[op]) {
// We should be good to go. // We should be good to go.
req.suffix = route.dn; req.suffix = route.dn;

View File

@ -143,3 +143,18 @@ test('rdn spacing', function (t) {
t.equals(dn2.spaced(false).toString(), 'cn=foo,dc=bar'); t.equals(dn2.spaced(false).toString(), 'cn=foo,dc=bar');
t.end(); t.end();
}); });
test('isDN duck-testing', function (t) {
var valid = dn.parse('cn=foo');
var isDN = dn.DN.isDN;
t.notOk(isDN(null));
t.notOk(isDN('cn=foo'));
t.ok(isDN(valid));
var duck = {
rdns: [ {look: 'ma'}, {a: 'dn'} ],
toString: function () { return 'look=ma, a=dn'; }
};
t.ok(isDN(duck));
t.end();
});

View File

@ -53,8 +53,6 @@ test('parse', function (t) {
t.ok(req._parse(new BerReader(ber.buffer))); t.ok(req._parse(new BerReader(ber.buffer)));
t.equal(req.version, 3); t.equal(req.version, 3);
t.equal(req.dn.toString(), 'cn=root'); t.equal(req.dn.toString(), 'cn=root');
t.ok(req.name.constructor);
t.equal(req.name.constructor.name, 'DN');
t.equal(req.credentials, 'secret'); t.equal(req.credentials, 'secret');
t.end(); t.end();
}); });

View File

@ -36,7 +36,7 @@ test('new with args', function (t) {
value: 'testy' value: 'testy'
}); });
t.ok(req); t.ok(req);
t.equal(req.dn, 'cn=foo, o=test'); t.equal(req.dn.toString(), 'cn=foo, o=test');
t.equal(req.attribute, 'sn'); t.equal(req.attribute, 'sn');
t.equal(req.value, 'testy'); t.equal(req.value, 'testy');
t.end(); t.end();

View File

@ -196,3 +196,36 @@ test('route unbind', function (t) {
}); });
}); });
}); });
test('non-strict route', function (t) {
server = ldap.createServer({
strictDN: false
});
sock = getSock();
var testDN = 'this ain\'t a DN';
// invalid DNs go to default handler
server.search('', function (req, res, next) {
t.ok(req.dn);
t.equal(typeof (req.dn), 'string');
t.equal(req.dn, testDN);
res.end();
next();
});
server.listen(sock, function () {
t.ok(true, 'server startup');
var clt = ldap.createClient({
socketPath: sock,
strictDN: false
});
clt.search(testDN, {scope: 'base'}, function (err, res) {
t.ifError(err);
res.on('end', function () {
clt.destroy();
server.close();
t.end();
});
});
});
});