server.test.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. 'use strict'
  2. const net = require('net')
  3. const tap = require('tap')
  4. const vasync = require('vasync')
  5. const vm = require('node:vm')
  6. const { getSock } = require('./utils')
  7. const ldap = require('../lib')
  8. const SERVER_PORT = process.env.SERVER_PORT || 1389
  9. const SUFFIX = 'dc=test'
  10. tap.beforeEach(function (t) {
  11. // We do not need a `.afterEach` to clean up the sock files because that
  12. // is done when the server is destroyed.
  13. t.context.sock = getSock()
  14. })
  15. tap.test('basic create', function (t) {
  16. const server = ldap.createServer()
  17. t.ok(server)
  18. t.end()
  19. })
  20. tap.test('connection count', function (t) {
  21. const server = ldap.createServer()
  22. t.ok(server)
  23. server.listen(0, '127.0.0.1', function () {
  24. t.ok(true, 'server listening on ' + server.url)
  25. server.getConnections(function (err, count) {
  26. t.error(err)
  27. t.equal(count, 0)
  28. const client = ldap.createClient({ url: server.url })
  29. client.on('connect', function () {
  30. t.ok(true, 'client connected')
  31. server.getConnections(function (err, count) {
  32. t.error(err)
  33. t.equal(count, 1)
  34. client.unbind()
  35. server.close(() => t.end())
  36. })
  37. })
  38. })
  39. })
  40. })
  41. tap.test('properties', function (t) {
  42. const server = ldap.createServer()
  43. t.equal(server.name, 'LDAPServer')
  44. // TODO: better test
  45. server.maxConnections = 10
  46. t.equal(server.maxConnections, 10)
  47. t.equal(server.url, null, 'url empty before bind')
  48. // listen on a random port so we have a url
  49. server.listen(0, '127.0.0.1', function () {
  50. t.ok(server.url)
  51. server.close(() => t.end())
  52. })
  53. })
  54. tap.test('IPv6 URL is formatted correctly', function (t) {
  55. const server = ldap.createServer()
  56. t.equal(server.url, null, 'url empty before bind')
  57. server.listen(0, '::1', function () {
  58. t.ok(server.url)
  59. t.equal(server.url, 'ldap://[::1]:' + server.port)
  60. server.close(() => t.end())
  61. })
  62. })
  63. tap.test('listen on unix/named socket', function (t) {
  64. const server = ldap.createServer()
  65. server.listen(t.context.sock, function () {
  66. t.ok(server.url)
  67. t.equal(server.url.split(':')[0], 'ldapi')
  68. server.close(() => t.end())
  69. })
  70. })
  71. tap.test('listen on static port', function (t) {
  72. const server = ldap.createServer()
  73. server.listen(SERVER_PORT, '127.0.0.1', function () {
  74. const addr = server.address()
  75. t.equal(addr.port, parseInt(SERVER_PORT, 10))
  76. t.equal(server.url, `ldap://127.0.0.1:${SERVER_PORT}`)
  77. server.close(() => t.end())
  78. })
  79. })
  80. tap.test('listen on ephemeral port', function (t) {
  81. const server = ldap.createServer()
  82. server.listen(0, '127.0.0.1', function () {
  83. const addr = server.address()
  84. t.ok(addr.port > 0)
  85. t.ok(addr.port < 65535)
  86. server.close(() => t.end())
  87. })
  88. })
  89. tap.test('route order', function (t) {
  90. function generateHandler (response) {
  91. const func = function handler (req, res, next) {
  92. res.send({
  93. dn: response,
  94. attributes: { }
  95. })
  96. res.end()
  97. return next()
  98. }
  99. return func
  100. }
  101. const server = ldap.createServer()
  102. const sock = t.context.sock
  103. const dnShort = SUFFIX
  104. const dnMed = 'dc=sub,' + SUFFIX
  105. const dnLong = 'dc=long,dc=sub,' + SUFFIX
  106. // Mount routes out of order
  107. server.search(dnMed, generateHandler(dnMed))
  108. server.search(dnShort, generateHandler(dnShort))
  109. server.search(dnLong, generateHandler(dnLong))
  110. server.listen(sock, function () {
  111. t.ok(true, 'server listen')
  112. const client = ldap.createClient({ socketPath: sock })
  113. client.on('connect', () => {
  114. vasync.forEachParallel({
  115. func: runSearch,
  116. inputs: [dnShort, dnMed, dnLong]
  117. }, function (err) {
  118. t.error(err)
  119. client.unbind()
  120. server.close(() => t.end())
  121. })
  122. })
  123. function runSearch (value, cb) {
  124. client.search(value, '(objectclass=*)', function (err, res) {
  125. t.error(err)
  126. t.ok(res)
  127. res.on('searchEntry', function (entry) {
  128. t.equal(entry.dn.toString(), value)
  129. })
  130. res.on('end', function () {
  131. cb()
  132. })
  133. })
  134. }
  135. })
  136. })
  137. tap.test('route absent', function (t) {
  138. const server = ldap.createServer()
  139. const DN_ROUTE = 'dc=base'
  140. const DN_MISSING = 'dc=absent'
  141. server.bind(DN_ROUTE, function (req, res, next) {
  142. res.end()
  143. return next()
  144. })
  145. server.listen(t.context.sock, function () {
  146. t.ok(true, 'server startup')
  147. vasync.parallel({
  148. funcs: [
  149. function presentBind (cb) {
  150. const clt = ldap.createClient({ socketPath: t.context.sock })
  151. clt.bind(DN_ROUTE, '', function (err) {
  152. t.notOk(err)
  153. clt.unbind()
  154. cb()
  155. })
  156. },
  157. function absentBind (cb) {
  158. const clt = ldap.createClient({ socketPath: t.context.sock })
  159. clt.bind(DN_MISSING, '', function (err) {
  160. t.ok(err)
  161. t.equal(err.code, ldap.LDAP_NO_SUCH_OBJECT)
  162. clt.unbind()
  163. cb()
  164. })
  165. }
  166. ]
  167. }, function (err) {
  168. t.notOk(err)
  169. server.close(() => t.end())
  170. })
  171. })
  172. })
  173. tap.test('route unbind', function (t) {
  174. const server = ldap.createServer()
  175. server.unbind(function (req, res, next) {
  176. t.ok(true, 'server unbind successful')
  177. res.end()
  178. return next()
  179. })
  180. server.listen(t.context.sock, function () {
  181. t.ok(true, 'server startup')
  182. const client = ldap.createClient({ socketPath: t.context.sock })
  183. client.bind('', '', function (err) {
  184. t.error(err, 'client bind error')
  185. client.unbind(function (err) {
  186. t.error(err, 'client unbind error')
  187. server.close(() => t.end())
  188. })
  189. })
  190. })
  191. })
  192. tap.test('bind/unbind identity anonymous', function (t) {
  193. const server = ldap.createServer({
  194. connectionRouter: function (c) {
  195. server.newConnection(c)
  196. server.emit('testconnection', c)
  197. }
  198. })
  199. server.unbind(function (req, res, next) {
  200. t.ok(true, 'server unbind successful')
  201. res.end()
  202. return next()
  203. })
  204. server.bind('', function (req, res, next) {
  205. t.ok(true, 'server bind successful')
  206. res.end()
  207. return next()
  208. })
  209. const anonDN = ldap.parseDN('cn=anonymous')
  210. server.listen(t.context.sock, function () {
  211. t.ok(true, 'server startup')
  212. const client = ldap.createClient({ socketPath: t.context.sock })
  213. server.once('testconnection', (c) => {
  214. t.ok(anonDN.equals(c.ldap.bindDN), 'pre bind dn is correct')
  215. client.bind('', '', function (err) {
  216. t.error(err, 'client anon bind error')
  217. t.ok(anonDN.equals(c.ldap.bindDN), 'anon bind dn is correct')
  218. client.unbind(function (err) {
  219. t.error(err, 'client anon unbind error')
  220. t.ok(anonDN.equals(c.ldap.bindDN), 'anon unbind dn is correct')
  221. server.close(() => t.end())
  222. })
  223. })
  224. })
  225. })
  226. })
  227. tap.test('does not crash on empty DN values', function (t) {
  228. const server = ldap.createServer({
  229. connectionRouter: function (c) {
  230. server.newConnection(c)
  231. server.emit('testconnection', c)
  232. }
  233. })
  234. server.listen(t.context.sock, function () {
  235. const client = ldap.createClient({ socketPath: t.context.sock })
  236. server.once('testconnection', () => {
  237. client.bind('', 'pw', function (err) {
  238. t.ok(err, 'blank bind dn throws error')
  239. client.unbind(function () {
  240. server.close(() => t.end())
  241. })
  242. })
  243. })
  244. })
  245. })
  246. tap.test('bind/unbind identity user', function (t) {
  247. const server = ldap.createServer({
  248. connectionRouter: function (c) {
  249. server.newConnection(c)
  250. server.emit('testconnection', c)
  251. }
  252. })
  253. server.unbind(function (req, res, next) {
  254. t.ok(true, 'server unbind successful')
  255. res.end()
  256. return next()
  257. })
  258. server.bind('', function (req, res, next) {
  259. t.ok(true, 'server bind successful')
  260. res.end()
  261. return next()
  262. })
  263. const anonDN = ldap.parseDN('cn=anonymous')
  264. const testDN = ldap.parseDN('cn=anotheruser')
  265. server.listen(t.context.sock, function () {
  266. t.ok(true, 'server startup')
  267. const client = ldap.createClient({ socketPath: t.context.sock })
  268. server.once('testconnection', (c) => {
  269. t.ok(anonDN.equals(c.ldap.bindDN), 'pre bind dn is correct')
  270. client.bind(testDN.toString(), 'somesecret', function (err) {
  271. t.error(err, 'user bind error')
  272. t.ok(testDN.equals(c.ldap.bindDN), 'user bind dn is correct')
  273. // check rebinds too
  274. client.bind('', '', function (err) {
  275. t.error(err, 'client anon bind error')
  276. t.ok(anonDN.equals(c.ldap.bindDN), 'anon bind dn is correct')
  277. // user rebind
  278. client.bind(testDN.toString(), 'somesecret', function (err) {
  279. t.error(err, 'user bind error')
  280. t.ok(testDN.equals(c.ldap.bindDN), 'user rebind dn is correct')
  281. client.unbind(function (err) {
  282. t.error(err, 'user unbind error')
  283. t.ok(anonDN.equals(c.ldap.bindDN), 'user unbind dn is correct')
  284. server.close(() => t.end())
  285. })
  286. })
  287. })
  288. })
  289. })
  290. })
  291. })
  292. tap.test('strict routing', function (t) {
  293. const testDN = 'cn=valid'
  294. let clt
  295. let server
  296. const sock = t.context.sock
  297. vasync.pipeline({
  298. funcs: [
  299. function setup (_, cb) {
  300. server = ldap.createServer({})
  301. // invalid DNs would go to default handler
  302. server.search('', function (req, res, next) {
  303. t.ok(req.dn)
  304. t.equal(typeof (req.dn), 'object')
  305. t.equal(req.dn.toString(), testDN)
  306. res.end()
  307. next()
  308. })
  309. server.listen(sock, function () {
  310. t.ok(true, 'server startup')
  311. clt = ldap.createClient({
  312. socketPath: sock
  313. })
  314. cb()
  315. })
  316. },
  317. function testGood (_, cb) {
  318. clt.search(testDN, { scope: 'base' }, function (err, res) {
  319. t.error(err)
  320. res.once('error', function (err2) {
  321. t.error(err2)
  322. cb(err2)
  323. })
  324. res.once('end', function (result) {
  325. t.ok(result, 'accepted invalid dn')
  326. cb()
  327. })
  328. })
  329. }
  330. ]
  331. }, function (err) {
  332. t.error(err)
  333. if (clt) {
  334. clt.destroy()
  335. }
  336. server.close(() => t.end())
  337. })
  338. })
  339. tap.test('close accept a callback', function (t) {
  340. const server = ldap.createServer()
  341. // callback is called when the server is closed
  342. server.listen(0, function (err) {
  343. t.error(err)
  344. server.close(function (err) {
  345. t.error(err)
  346. t.end()
  347. })
  348. })
  349. })
  350. tap.test('close without error calls callback', function (t) {
  351. const server = ldap.createServer()
  352. // when the server is closed without error, the callback parameter is undefined
  353. server.listen(1389, '127.0.0.1', function (err) {
  354. t.error(err)
  355. server.close(function (err) {
  356. t.error(err)
  357. t.end()
  358. })
  359. })
  360. })
  361. tap.test('close passes error to callback', function (t) {
  362. const server = ldap.createServer()
  363. // when the server is closed with an error, the error is the first parameter of the callback
  364. server.close(function (err) {
  365. t.ok(err)
  366. t.end()
  367. })
  368. })
  369. tap.test('multithreading support via external server', function (t) {
  370. const serverOptions = { }
  371. const server = ldap.createServer(serverOptions)
  372. const fauxServer = net.createServer(serverOptions, (connection) => {
  373. server.newConnection(connection)
  374. })
  375. fauxServer.log = serverOptions.log
  376. fauxServer.ldap = {
  377. config: serverOptions
  378. }
  379. t.ok(server)
  380. fauxServer.listen(5555, '127.0.0.1', function () {
  381. t.ok(true, 'server listening on ' + server.url)
  382. t.ok(fauxServer)
  383. const client = ldap.createClient({ url: 'ldap://127.0.0.1:5555' })
  384. client.on('connect', function () {
  385. t.ok(client)
  386. client.unbind()
  387. fauxServer.close(() => t.end())
  388. })
  389. })
  390. })
  391. tap.test('multithreading support via hook', function (t) {
  392. const serverOptions = {
  393. connectionRouter: (connection) => {
  394. server.newConnection(connection)
  395. }
  396. }
  397. const server = ldap.createServer(serverOptions)
  398. const fauxServer = ldap.createServer(serverOptions)
  399. t.ok(server)
  400. fauxServer.listen(0, '127.0.0.1', function () {
  401. t.ok(true, 'server listening on ' + server.url)
  402. t.ok(fauxServer)
  403. const client = ldap.createClient({ url: fauxServer.url })
  404. client.on('connect', function () {
  405. t.ok(client)
  406. client.unbind()
  407. fauxServer.close(() => t.end())
  408. })
  409. })
  410. })
  411. tap.test('cross-realm type checks', function (t) {
  412. const server = ldap.createServer()
  413. const ctx = vm.createContext({})
  414. vm.runInContext(
  415. 'globalThis.search=function(){};\n' +
  416. 'globalThis.searches=[function(){}];'
  417. , ctx)
  418. server.search('', ctx.search)
  419. server.search('', ctx.searches)
  420. t.ok(server)
  421. t.end()
  422. })