123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- 'use strict'
- const net = require('net')
- const tap = require('tap')
- const vasync = require('vasync')
- const vm = require('node:vm')
- 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, '127.0.0.1', 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, '127.0.0.1', 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.parseDN('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('does not crash on empty DN values', function (t) {
- const server = ldap.createServer({
- connectionRouter: function (c) {
- server.newConnection(c)
- server.emit('testconnection', c)
- }
- })
- server.listen(t.context.sock, function () {
- const client = ldap.createClient({ socketPath: t.context.sock })
- server.once('testconnection', () => {
- client.bind('', 'pw', function (err) {
- t.ok(err, 'blank bind dn throws error')
- client.unbind(function () {
- 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.parseDN('cn=anonymous')
- const testDN = ldap.parseDN('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({})
- // 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
- })
- cb()
- })
- },
- 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('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())
- })
- })
- })
- tap.test('cross-realm type checks', function (t) {
- const server = ldap.createServer()
- const ctx = vm.createContext({})
- vm.runInContext(
- 'globalThis.search=function(){};\n' +
- 'globalThis.searches=[function(){}];'
- , ctx)
- server.search('', ctx.search)
- server.search('', ctx.searches)
- t.ok(server)
- t.end()
- })
|