2014-01-22 18:21:57 +00:00
|
|
|
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
|
|
|
|
2019-08-25 01:04:23 +00:00
|
|
|
var logger = Object.create(require('abstract-logging'));
|
2014-01-22 18:21:57 +00:00
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
var fs = require('fs')
|
|
|
|
var tap = require('tap');
|
2019-08-25 17:25:51 +00:00
|
|
|
var uuid = require('uuid');
|
2014-01-22 18:21:57 +00:00
|
|
|
var vasync = require('vasync');
|
|
|
|
|
|
|
|
|
|
|
|
///--- Globals
|
|
|
|
|
|
|
|
var BIND_DN = 'cn=root';
|
|
|
|
var BIND_PW = 'secret';
|
|
|
|
|
|
|
|
var SUFFIX = 'dc=test';
|
|
|
|
|
2014-06-06 17:21:05 +00:00
|
|
|
var SERVER_PORT = process.env.SERVER_PORT || 1389;
|
|
|
|
|
2014-01-22 18:21:57 +00:00
|
|
|
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
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.beforeEach(function (done, t) {
|
|
|
|
// We do not need a `.afterEach` to clean up the sock files because that
|
|
|
|
// is done when the server is destroyed.
|
|
|
|
t.context.sock = getSock()
|
|
|
|
done()
|
|
|
|
})
|
|
|
|
|
|
|
|
tap.test('load library', function (t) {
|
2014-01-22 18:21:57 +00:00
|
|
|
ldap = require('../lib/index');
|
|
|
|
t.ok(ldap.createServer);
|
|
|
|
t.end();
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('basic create', function (t) {
|
|
|
|
const server = ldap.createServer();
|
2014-01-22 18:21:57 +00:00
|
|
|
t.ok(server);
|
|
|
|
t.end();
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('properties', function (t) {
|
|
|
|
const server = ldap.createServer();
|
2015-10-31 13:34:51 +00:00
|
|
|
t.equal(server.name, 'LDAPServer');
|
|
|
|
|
|
|
|
// TODO: better test
|
|
|
|
server.maxConnections = 10;
|
|
|
|
t.equal(server.maxConnections, 10);
|
|
|
|
|
|
|
|
t.equal(server.url, null, 'url empty before bind');
|
|
|
|
// listen on a random port so we have a url
|
|
|
|
server.listen(0, 'localhost', function () {
|
|
|
|
t.ok(server.url);
|
|
|
|
|
|
|
|
server.close();
|
|
|
|
t.end();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('listen on unix/named socket', function (t) {
|
2015-10-31 13:34:51 +00:00
|
|
|
t.plan(2);
|
2019-08-27 13:08:00 +00:00
|
|
|
const server = ldap.createServer();
|
|
|
|
server.listen(t.context.sock, function () {
|
2015-10-31 13:34:51 +00:00
|
|
|
t.ok(server.url);
|
|
|
|
t.equal(server.url.split(':')[0], 'ldapi');
|
2014-01-22 18:21:57 +00:00
|
|
|
server.close();
|
2014-06-06 17:21:05 +00:00
|
|
|
t.end();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('listen on static port', function (t) {
|
2014-06-16 14:40:07 +00:00
|
|
|
t.plan(2);
|
2019-08-27 13:08:00 +00:00
|
|
|
const server = ldap.createServer();
|
2014-06-06 17:21:05 +00:00
|
|
|
server.listen(SERVER_PORT, '127.0.0.1', function () {
|
|
|
|
var addr = server.address();
|
|
|
|
t.equal(addr.port, parseInt(SERVER_PORT, 10));
|
|
|
|
t.equals(server.url, 'ldap://127.0.0.1:' + SERVER_PORT);
|
|
|
|
server.close();
|
|
|
|
t.end();
|
2014-01-22 18:21:57 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('listen on ephemeral port', function (t) {
|
2014-06-16 14:40:07 +00:00
|
|
|
t.plan(2);
|
2019-08-27 13:08:00 +00:00
|
|
|
const server = ldap.createServer();
|
2014-01-22 18:21:57 +00:00
|
|
|
server.listen(0, 'localhost', function () {
|
|
|
|
var addr = server.address();
|
|
|
|
t.ok(addr.port > 0);
|
|
|
|
t.ok(addr.port < 65535);
|
|
|
|
server.close();
|
2014-06-06 17:21:05 +00:00
|
|
|
t.end();
|
2014-01-22 18:21:57 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('route order', function (t) {
|
2014-01-22 18:21:57 +00:00
|
|
|
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();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('route absent', function (t) {
|
|
|
|
const server = ldap.createServer();
|
2014-01-22 18:21:57 +00:00
|
|
|
var DN_ROUTE = 'dc=base';
|
|
|
|
var DN_MISSING = 'dc=absent';
|
|
|
|
|
|
|
|
server.bind(DN_ROUTE, function (req, res, next) {
|
|
|
|
res.end();
|
|
|
|
return next();
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
server.listen(t.context.sock, function () {
|
2014-01-22 18:21:57 +00:00
|
|
|
t.ok(true, 'server startup');
|
|
|
|
vasync.parallel({
|
|
|
|
'funcs': [
|
|
|
|
function presentBind(cb) {
|
2019-08-27 13:08:00 +00:00
|
|
|
var clt = ldap.createClient({ socketPath: t.context.sock });
|
2014-01-22 18:21:57 +00:00
|
|
|
clt.bind(DN_ROUTE, '', function (err) {
|
|
|
|
t.notOk(err);
|
|
|
|
clt.unbind();
|
|
|
|
cb();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function absentBind(cb) {
|
2019-08-27 13:08:00 +00:00
|
|
|
var clt = ldap.createClient({ socketPath: t.context.sock });
|
2014-01-22 18:21:57 +00:00
|
|
|
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();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2014-06-06 19:05:08 +00:00
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('route unbind', function (t) {
|
2014-06-06 19:05:08 +00:00
|
|
|
t.plan(4);
|
2019-08-27 13:08:00 +00:00
|
|
|
const server = ldap.createServer();
|
2014-06-06 19:05:08 +00:00
|
|
|
|
|
|
|
server.unbind(function (req, res, next) {
|
|
|
|
t.ok(true, 'server unbind successful');
|
|
|
|
res.end();
|
|
|
|
return next();
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
server.listen(t.context.sock, function () {
|
2014-06-06 19:05:08 +00:00
|
|
|
t.ok(true, 'server startup');
|
2019-08-27 13:08:00 +00:00
|
|
|
client = ldap.createClient({ socketPath: t.context.sock });
|
2014-06-06 19:05:08 +00:00
|
|
|
client.bind('', '', function (err) {
|
|
|
|
t.ifError(err, 'client bind error');
|
|
|
|
client.unbind(function (err) {
|
|
|
|
t.ifError(err, 'client unbind error');
|
|
|
|
server.close();
|
|
|
|
t.end();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2014-09-30 23:39:19 +00:00
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('strict routing', function (t) {
|
2015-10-31 14:01:50 +00:00
|
|
|
var testDN = 'cn=valid';
|
|
|
|
var clt;
|
2019-08-27 13:08:00 +00:00
|
|
|
var server;
|
|
|
|
var sock = t.context.sock;
|
2015-10-31 14:01:50 +00:00
|
|
|
vasync.pipeline({
|
|
|
|
funcs: [
|
|
|
|
function setup(_, cb) {
|
|
|
|
server = ldap.createServer({
|
|
|
|
// strictDN: true - on by default
|
|
|
|
});
|
|
|
|
// invalid DNs would go to default handler
|
|
|
|
server.search('', function (req, res, next) {
|
|
|
|
t.ok(req.dn);
|
|
|
|
t.equal(typeof (req.dn), 'object');
|
|
|
|
t.equal(req.dn.toString(), testDN);
|
|
|
|
res.end();
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
server.listen(sock, function () {
|
|
|
|
t.ok(true, 'server startup');
|
|
|
|
clt = ldap.createClient({
|
|
|
|
socketPath: sock,
|
|
|
|
strictDN: false
|
|
|
|
});
|
|
|
|
cb();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function testBad(_, cb) {
|
|
|
|
clt.search('not a dn', {scope: 'base'}, function (err, res) {
|
|
|
|
t.ifError(err);
|
|
|
|
res.once('error', function (err2) {
|
|
|
|
t.ok(err2);
|
|
|
|
t.equal(err2.code, ldap.LDAP_INVALID_DN_SYNTAX);
|
|
|
|
cb();
|
|
|
|
});
|
|
|
|
res.once('end', function () {
|
|
|
|
t.fail('accepted invalid dn');
|
|
|
|
cb('bogus');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function testGood(_, cb) {
|
|
|
|
clt.search(testDN, {scope: 'base'}, function (err, res) {
|
|
|
|
t.ifError(err);
|
|
|
|
res.once('error', function (err2) {
|
|
|
|
t.ifError(err2);
|
|
|
|
cb(err2);
|
|
|
|
});
|
|
|
|
res.once('end', function (result) {
|
|
|
|
t.ok(result, 'accepted invalid dn');
|
|
|
|
cb();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}, function (err, res) {
|
|
|
|
if (clt) {
|
|
|
|
clt.destroy();
|
|
|
|
}
|
|
|
|
server.close();
|
|
|
|
t.end();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('non-strict routing', function (t) {
|
|
|
|
const server = ldap.createServer({
|
2014-09-30 23:39:19 +00:00
|
|
|
strictDN: false
|
|
|
|
});
|
|
|
|
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();
|
|
|
|
});
|
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
server.listen(t.context.sock, function () {
|
2014-09-30 23:39:19 +00:00
|
|
|
t.ok(true, 'server startup');
|
|
|
|
var clt = ldap.createClient({
|
2019-08-27 13:08:00 +00:00
|
|
|
socketPath: t.context.sock,
|
2014-09-30 23:39:19 +00:00
|
|
|
strictDN: false
|
|
|
|
});
|
|
|
|
clt.search(testDN, {scope: 'base'}, function (err, res) {
|
|
|
|
t.ifError(err);
|
|
|
|
res.on('end', function () {
|
|
|
|
clt.destroy();
|
|
|
|
server.close();
|
|
|
|
t.end();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2019-08-27 13:08:00 +00:00
|
|
|
});
|
2019-08-27 11:57:29 +00:00
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('close accept a callback', function (t) {
|
|
|
|
const server = ldap.createServer();
|
|
|
|
// callback is called when the server is closed
|
|
|
|
server.close(function(err){
|
|
|
|
t.end();
|
2019-08-27 11:57:29 +00:00
|
|
|
});
|
2019-08-27 13:08:00 +00:00
|
|
|
});
|
2019-08-27 11:57:29 +00:00
|
|
|
|
2019-08-27 13:08:00 +00:00
|
|
|
tap.test('close without error calls callback', function (t) {
|
|
|
|
const server = ldap.createServer();
|
|
|
|
// when the server is closed without error, the callback parameter is undefined
|
|
|
|
server.listen(1389,'127.0.0.1',function(err){
|
2019-08-27 11:57:29 +00:00
|
|
|
server.close(function(err){
|
2019-08-27 13:08:00 +00:00
|
|
|
t.error(err);
|
2019-08-27 11:57:29 +00:00
|
|
|
t.end();
|
|
|
|
});
|
|
|
|
});
|
2014-09-30 23:39:19 +00:00
|
|
|
});
|
2019-08-27 13:08:00 +00:00
|
|
|
|
|
|
|
tap.test('close passes error to callback', function (t) {
|
|
|
|
const server = ldap.createServer();
|
|
|
|
// when the server is closed with an error, the error is the first parameter of the callback
|
|
|
|
server.close(function(err){
|
|
|
|
t.ok(err);
|
|
|
|
t.end();
|
|
|
|
});
|
|
|
|
});
|