node-ldapjs/test/server.test.js

485 lines
13 KiB
JavaScript

'use strict'
const net = require('net')
const tap = require('tap')
const vasync = require('vasync')
const { getSock } = require('./utils')
const ldap = require('../lib')
const SERVER_PORT = process.env.SERVER_PORT || 1389
const SUFFIX = 'dc=test'
tap.beforeEach(function (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()
})
tap.test('basic create', function (t) {
const server = ldap.createServer()
t.ok(server)
t.end()
})
tap.test('connection count', function (t) {
const server = ldap.createServer()
t.ok(server)
server.listen(0, '127.0.0.1', function () {
t.ok(true, 'server listening on ' + server.url)
server.getConnections(function (err, count) {
t.error(err)
t.equal(count, 0)
const client = ldap.createClient({ url: server.url })
client.on('connect', function () {
t.ok(true, 'client connected')
server.getConnections(function (err, count) {
t.error(err)
t.equal(count, 1)
client.unbind()
server.close(() => t.end())
})
})
})
})
})
tap.test('properties', function (t) {
const server = ldap.createServer()
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())
})
})
tap.test('IPv6 URL is formatted correctly', function (t) {
const server = ldap.createServer()
t.equal(server.url, null, 'url empty before bind')
server.listen(0, '::1', function () {
t.ok(server.url)
t.equal(server.url, 'ldap://[::1]:' + server.port)
server.close(() => t.end())
})
})
tap.test('listen on unix/named socket', function (t) {
const server = ldap.createServer()
server.listen(t.context.sock, function () {
t.ok(server.url)
t.equal(server.url.split(':')[0], 'ldapi')
server.close(() => t.end())
})
})
tap.test('listen on static port', function (t) {
const server = ldap.createServer()
server.listen(SERVER_PORT, '127.0.0.1', function () {
const addr = server.address()
t.equal(addr.port, parseInt(SERVER_PORT, 10))
t.equal(server.url, `ldap://127.0.0.1:${SERVER_PORT}`)
server.close(() => t.end())
})
})
tap.test('listen on ephemeral port', function (t) {
const server = ldap.createServer()
server.listen(0, 'localhost', function () {
const addr = server.address()
t.ok(addr.port > 0)
t.ok(addr.port < 65535)
server.close(() => t.end())
})
})
tap.test('route order', function (t) {
function generateHandler (response) {
const func = function handler (req, res, next) {
res.send({
dn: response,
attributes: { }
})
res.end()
return next()
}
return func
}
const server = ldap.createServer()
const sock = t.context.sock
const dnShort = SUFFIX
const dnMed = 'dc=sub,' + SUFFIX
const 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')
const client = ldap.createClient({ socketPath: sock })
client.on('connect', () => {
vasync.forEachParallel({
func: runSearch,
inputs: [dnShort, dnMed, dnLong]
}, function (err) {
t.error(err)
client.unbind()
server.close(() => t.end())
})
})
function runSearch (value, cb) {
client.search(value, '(objectclass=*)', function (err, res) {
t.error(err)
t.ok(res)
res.on('searchEntry', function (entry) {
t.equal(entry.dn.toString(), value)
})
res.on('end', function () {
cb()
})
})
}
})
})
tap.test('route absent', function (t) {
const server = ldap.createServer()
const DN_ROUTE = 'dc=base'
const DN_MISSING = 'dc=absent'
server.bind(DN_ROUTE, function (req, res, next) {
res.end()
return next()
})
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
vasync.parallel({
funcs: [
function presentBind (cb) {
const clt = ldap.createClient({ socketPath: t.context.sock })
clt.bind(DN_ROUTE, '', function (err) {
t.notOk(err)
clt.unbind()
cb()
})
},
function absentBind (cb) {
const clt = ldap.createClient({ socketPath: t.context.sock })
clt.bind(DN_MISSING, '', function (err) {
t.ok(err)
t.equal(err.code, ldap.LDAP_NO_SUCH_OBJECT)
clt.unbind()
cb()
})
}
]
}, function (err) {
t.notOk(err)
server.close(() => t.end())
})
})
})
tap.test('route unbind', function (t) {
const server = ldap.createServer()
server.unbind(function (req, res, next) {
t.ok(true, 'server unbind successful')
res.end()
return next()
})
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
const client = ldap.createClient({ socketPath: t.context.sock })
client.bind('', '', function (err) {
t.error(err, 'client bind error')
client.unbind(function (err) {
t.error(err, 'client unbind error')
server.close(() => t.end())
})
})
})
})
tap.test('bind/unbind identity anonymous', function (t) {
const server = ldap.createServer({
connectionRouter: function (c) {
server.newConnection(c)
server.emit('testconnection', c)
}
})
server.unbind(function (req, res, next) {
t.ok(true, 'server unbind successful')
res.end()
return next()
})
server.bind('', function (req, res, next) {
t.ok(true, 'server bind successful')
res.end()
return next()
})
const anonDN = ldap.dn.parse('cn=anonymous')
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
const client = ldap.createClient({ socketPath: t.context.sock })
server.once('testconnection', (c) => {
t.ok(anonDN.equals(c.ldap.bindDN), 'pre bind dn is correct')
client.bind('', '', function (err) {
t.error(err, 'client anon bind error')
t.ok(anonDN.equals(c.ldap.bindDN), 'anon bind dn is correct')
client.unbind(function (err) {
t.error(err, 'client anon unbind error')
t.ok(anonDN.equals(c.ldap.bindDN), 'anon unbind dn is correct')
server.close(() => t.end())
})
})
})
})
})
tap.test('bind/unbind identity user', function (t) {
const server = ldap.createServer({
connectionRouter: function (c) {
server.newConnection(c)
server.emit('testconnection', c)
}
})
server.unbind(function (req, res, next) {
t.ok(true, 'server unbind successful')
res.end()
return next()
})
server.bind('', function (req, res, next) {
t.ok(true, 'server bind successful')
res.end()
return next()
})
const anonDN = ldap.dn.parse('cn=anonymous')
const testDN = ldap.dn.parse('cn=anotheruser')
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
const client = ldap.createClient({ socketPath: t.context.sock })
server.once('testconnection', (c) => {
t.ok(anonDN.equals(c.ldap.bindDN), 'pre bind dn is correct')
client.bind(testDN.toString(), 'somesecret', function (err) {
t.error(err, 'user bind error')
t.ok(testDN.equals(c.ldap.bindDN), 'user bind dn is correct')
// check rebinds too
client.bind('', '', function (err) {
t.error(err, 'client anon bind error')
t.ok(anonDN.equals(c.ldap.bindDN), 'anon bind dn is correct')
// user rebind
client.bind(testDN.toString(), 'somesecret', function (err) {
t.error(err, 'user bind error')
t.ok(testDN.equals(c.ldap.bindDN), 'user rebind dn is correct')
client.unbind(function (err) {
t.error(err, 'user unbind error')
t.ok(anonDN.equals(c.ldap.bindDN), 'user unbind dn is correct')
server.close(() => t.end())
})
})
})
})
})
})
})
tap.test('strict routing', function (t) {
const testDN = 'cn=valid'
let clt
let server
const sock = t.context.sock
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.error(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(Error('bogus'))
})
})
},
function testGood (_, cb) {
clt.search(testDN, { scope: 'base' }, function (err, res) {
t.error(err)
res.once('error', function (err2) {
t.error(err2)
cb(err2)
})
res.once('end', function (result) {
t.ok(result, 'accepted invalid dn')
cb()
})
})
}
]
}, function (err) {
t.error(err)
if (clt) {
clt.destroy()
}
server.close(() => t.end())
})
})
tap.test('non-strict routing', function (t) {
const server = ldap.createServer({
strictDN: false
})
const 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(t.context.sock, function () {
t.ok(true, 'server startup')
const clt = ldap.createClient({
socketPath: t.context.sock,
strictDN: false
})
clt.search(testDN, { scope: 'base' }, function (err, res) {
t.error(err)
res.on('end', function () {
clt.destroy()
server.close(() => t.end())
})
})
})
})
tap.test('close accept a callback', function (t) {
const server = ldap.createServer()
// callback is called when the server is closed
server.listen(0, function (err) {
t.error(err)
server.close(function (err) {
t.error(err)
t.end()
})
})
})
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) {
t.error(err)
server.close(function (err) {
t.error(err)
t.end()
})
})
})
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()
})
})
tap.test('multithreading support via external server', function (t) {
const serverOptions = { }
const server = ldap.createServer(serverOptions)
const fauxServer = net.createServer(serverOptions, (connection) => {
server.newConnection(connection)
})
fauxServer.log = serverOptions.log
fauxServer.ldap = {
config: serverOptions
}
t.ok(server)
fauxServer.listen(5555, '127.0.0.1', function () {
t.ok(true, 'server listening on ' + server.url)
t.ok(fauxServer)
const client = ldap.createClient({ url: 'ldap://127.0.0.1:5555' })
client.on('connect', function () {
t.ok(client)
client.unbind()
fauxServer.close(() => t.end())
})
})
})
tap.test('multithreading support via hook', function (t) {
const serverOptions = {
connectionRouter: (connection) => {
server.newConnection(connection)
}
}
const server = ldap.createServer(serverOptions)
const fauxServer = ldap.createServer(serverOptions)
t.ok(server)
fauxServer.listen(0, '127.0.0.1', function () {
t.ok(true, 'server listening on ' + server.url)
t.ok(fauxServer)
const client = ldap.createClient({ url: fauxServer.url })
client.on('connect', function () {
t.ok(client)
client.unbind()
fauxServer.close(() => t.end())
})
})
})