client.test.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789
  1. 'use strict'
  2. const util = require('util')
  3. const assert = require('assert')
  4. const tap = require('tap')
  5. const vasync = require('vasync')
  6. const getPort = require('get-port')
  7. const { getSock, uuid } = require('./utils')
  8. const Attribute = require('@ldapjs/attribute')
  9. const Change = require('@ldapjs/change')
  10. const messages = require('@ldapjs/messages')
  11. const controls = require('@ldapjs/controls')
  12. const dn = require('@ldapjs/dn')
  13. const ldap = require('../lib')
  14. const {
  15. SearchRequest,
  16. SearchResultEntry,
  17. SearchResultReference,
  18. SearchResultDone
  19. } = messages
  20. const SUFFIX = 'dc=test'
  21. const LDAP_CONNECT_TIMEOUT = process.env.LDAP_CONNECT_TIMEOUT || 0
  22. const BIND_DN = 'cn=root'
  23. const BIND_PW = 'secret'
  24. tap.beforeEach((t) => {
  25. return new Promise(resolve => {
  26. t.context.socketPath = getSock()
  27. t.context.server = ldap.createServer()
  28. const server = t.context.server
  29. server.bind(BIND_DN, function (req, res, next) {
  30. if (req.credentials !== BIND_PW) { return next(new ldap.InvalidCredentialsError('Invalid password')) }
  31. res.end()
  32. return next()
  33. })
  34. server.add(SUFFIX, function (req, res, next) {
  35. res.end()
  36. return next()
  37. })
  38. server.compare(SUFFIX, function (req, res, next) {
  39. res.end(req.value === 'test')
  40. return next()
  41. })
  42. server.del(SUFFIX, function (req, res, next) {
  43. res.end()
  44. return next()
  45. })
  46. // LDAP whoami
  47. server.exop('1.3.6.1.4.1.4203.1.11.3', function (req, res, next) {
  48. res.responseValue = 'u:xxyyz@EXAMPLE.NET'
  49. res.end()
  50. return next()
  51. })
  52. server.modify(SUFFIX, function (req, res, next) {
  53. res.end()
  54. return next()
  55. })
  56. server.modifyDN(SUFFIX, function (req, res, next) {
  57. res.end()
  58. return next()
  59. })
  60. server.modifyDN('cn=issue-480', function (req, res, next) {
  61. assert(req.newRdn.toString().length > 132)
  62. res.end()
  63. return next()
  64. })
  65. server.search('dc=slow', function (req, res, next) {
  66. res.send({
  67. dn: 'dc=slow',
  68. attributes: {
  69. you: 'wish',
  70. this: 'was',
  71. faster: '.'
  72. }
  73. })
  74. setTimeout(function () {
  75. res.end()
  76. next()
  77. }, 250)
  78. })
  79. server.search('dc=timeout', function () {
  80. // Cause the client to timeout by not sending a response.
  81. })
  82. server.search(SUFFIX, function (req, res, next) {
  83. if (req.dn.equals('cn=ref,' + SUFFIX)) {
  84. res.send(res.createSearchReference('ldap://localhost'))
  85. } else if (req.dn.equals('cn=bin,' + SUFFIX)) {
  86. const attributes = []
  87. attributes.push(new Attribute({ type: 'foo;binary', values: ['wr0gKyDCvCA9IMK+'] }))
  88. attributes.push(new Attribute({ type: 'gb18030', values: [Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA])] }))
  89. attributes.push(new Attribute({ type: 'objectclass', values: ['binary'] }))
  90. res.send(res.createSearchEntry({
  91. objectName: req.dn,
  92. attributes
  93. }))
  94. } else {
  95. const attributes = []
  96. attributes.push(new Attribute({ type: 'cn', values: ['unit', 'test'] }))
  97. attributes.push(new Attribute({ type: 'SN', values: ['testy'] }))
  98. const e = res.createSearchEntry({
  99. objectName: req.dn,
  100. attributes
  101. })
  102. res.send(e)
  103. res.send(e)
  104. }
  105. res.end()
  106. return next()
  107. })
  108. server.search('cn=sizelimit', function (req, res, next) {
  109. const sizeLimit = 200
  110. for (let i = 0; i < 1000; i++) {
  111. if (req.sizeLimit > 0 && i >= req.sizeLimit) {
  112. break
  113. } else if (i > sizeLimit) {
  114. res.end(ldap.LDAP_SIZE_LIMIT_EXCEEDED)
  115. return next()
  116. }
  117. res.send({
  118. dn: util.format('o=%d, cn=sizelimit', i),
  119. attributes: {
  120. o: [i],
  121. objectclass: ['pagedResult']
  122. }
  123. })
  124. }
  125. res.end()
  126. return next()
  127. })
  128. server.search('cn=paged', function (req, res, next) {
  129. const min = 0
  130. const max = 1000
  131. function sendResults (start, end) {
  132. start = (start < min) ? min : start
  133. end = (end > max || end < min) ? max : end
  134. let i
  135. for (i = start; i < end; i++) {
  136. res.send(new SearchResultEntry({
  137. messageId: res.id,
  138. entry: `o=${i},cn=paged`,
  139. attributes: Attribute.fromObject({
  140. o: [i],
  141. objectclass: ['pagedResult']
  142. })
  143. }))
  144. }
  145. return i
  146. }
  147. let cookie = null
  148. let pageSize = 0
  149. req.controls.forEach(function (control) {
  150. if (control.type === controls.PagedResultsControl.OID) {
  151. pageSize = control.value.size
  152. cookie = control.value.cookie
  153. }
  154. })
  155. if (!cookie || Buffer.isBuffer(cookie) === false) {
  156. // don't allow non-paged searches for this test endpoint
  157. next(Error('unwilling to perform'))
  158. }
  159. // Do simple paging
  160. let first = min
  161. if (cookie.length !== 0) {
  162. first = parseInt(cookie.toString(), 10)
  163. }
  164. const last = sendResults(first, first + pageSize)
  165. let resultCookie
  166. if (last < max) {
  167. resultCookie = Buffer.from(last.toString())
  168. } else {
  169. resultCookie = Buffer.from('')
  170. }
  171. res.addControl(new controls.PagedResultsControl({
  172. value: {
  173. size: pageSize, // correctness not required here
  174. cookie: resultCookie
  175. }
  176. }))
  177. res.end()
  178. next()
  179. })
  180. server.search('cn=sssvlv', function (req, res, next) {
  181. const min = 0
  182. const max = 100
  183. const results = []
  184. let o = 'aa'
  185. for (let i = min; i < max; i++) {
  186. results.push({
  187. dn: util.format('o=%s, cn=sssvlv', o),
  188. attributes: {
  189. o: [o],
  190. objectclass: ['sssvlvResult']
  191. }
  192. })
  193. o = ((parseInt(o, 36) + 1).toString(36)).replace(/0/g, 'a')
  194. }
  195. function sendResults (start, end, sortBy, sortDesc) {
  196. start = (start < min) ? min : start
  197. end = (end > max || end < min) ? max : end
  198. const sorted = results.sort((a, b) => {
  199. if (a.attributes[sortBy][0] < b.attributes[sortBy][0]) {
  200. return sortDesc ? 1 : -1
  201. } else if (a.attributes[sortBy][0] > b.attributes[sortBy][0]) {
  202. return sortDesc ? -1 : 1
  203. }
  204. return 0
  205. })
  206. for (let i = start; i < end; i++) {
  207. res.send(sorted[i])
  208. }
  209. }
  210. let sortBy = null
  211. let sortDesc = null
  212. let afterCount = null
  213. let targetOffset = null
  214. req.controls.forEach(function (control) {
  215. if (control.type === ldap.ServerSideSortingRequestControl.OID) {
  216. sortBy = control.value[0].attributeType
  217. sortDesc = control.value[0].reverseOrder
  218. }
  219. if (control.type === ldap.VirtualListViewRequestControl.OID) {
  220. afterCount = control.value.afterCount
  221. targetOffset = control.value.targetOffset
  222. }
  223. })
  224. if (sortBy) {
  225. if (afterCount && targetOffset) {
  226. sendResults(targetOffset - 1, (targetOffset + afterCount), sortBy, sortDesc)
  227. } else {
  228. sendResults(min, max, sortBy, sortDesc)
  229. }
  230. res.end()
  231. next()
  232. } else {
  233. next(new ldap.UnwillingToPerformError())
  234. }
  235. })
  236. server.search('cn=pagederr', function (req, res, next) {
  237. let cookie = null
  238. req.controls.forEach(function (control) {
  239. if (control.type === ldap.PagedResultsControl.OID) {
  240. cookie = control.value.cookie
  241. }
  242. })
  243. if (cookie && Buffer.isBuffer(cookie) && cookie.length === 0) {
  244. // send first "page"
  245. res.send({
  246. dn: util.format('o=result, cn=pagederr'),
  247. attributes: {
  248. o: 'result',
  249. objectclass: ['pagedResult']
  250. }
  251. })
  252. res.controls.push(new ldap.PagedResultsControl({
  253. value: {
  254. size: 2,
  255. cookie: Buffer.from('a')
  256. }
  257. }))
  258. res.end()
  259. return next()
  260. } else {
  261. // send error instead of second page
  262. res.end(ldap.LDAP_SIZE_LIMIT_EXCEEDED)
  263. return next()
  264. }
  265. })
  266. server.search('dc=empty', function (req, res, next) {
  267. res.send({
  268. dn: 'dc=empty',
  269. attributes: {
  270. member: [],
  271. 'member;range=0-1': ['cn=user1, dc=empty', 'cn=user2, dc=empty']
  272. }
  273. })
  274. res.end()
  275. return next()
  276. })
  277. server.search('cn=busy', function (req, res, next) {
  278. next(new ldap.BusyError('too much to do'))
  279. })
  280. server.search('', function (req, res, next) {
  281. if (req.dn.toString() === '') {
  282. res.send({
  283. dn: '',
  284. attributes: {
  285. objectclass: ['RootDSE', 'top']
  286. }
  287. })
  288. res.end()
  289. } else {
  290. // Turn away any other requests (since '' is the fallthrough route)
  291. res.errorMessage = 'No tree found for: ' + req.dn.toString()
  292. res.end(ldap.LDAP_NO_SUCH_OBJECT)
  293. }
  294. return next()
  295. })
  296. server.unbind(function (req, res, next) {
  297. res.end()
  298. return next()
  299. })
  300. server.listen(t.context.socketPath, function () {
  301. const client = ldap.createClient({
  302. connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10),
  303. socketPath: t.context.socketPath
  304. })
  305. t.context.client = client
  306. client.on('connect', () => resolve())
  307. })
  308. })
  309. })
  310. tap.afterEach((t) => {
  311. return new Promise(resolve => {
  312. t.context.client.unbind((err) => {
  313. t.error(err)
  314. t.context.server.close(() => resolve())
  315. })
  316. })
  317. })
  318. tap.test('createClient', t => {
  319. t.test('requires an options object', async t => {
  320. const match = /options.+required/
  321. t.throws(() => ldap.createClient(), match)
  322. t.throws(() => ldap.createClient([]), match)
  323. t.throws(() => ldap.createClient(''), match)
  324. t.throws(() => ldap.createClient(42), match)
  325. })
  326. t.test('url must be a string or array', async t => {
  327. const match = /options\.url \(string\|array\) required/
  328. t.throws(() => ldap.createClient({ url: {} }), match)
  329. t.throws(() => ldap.createClient({ url: 42 }), match)
  330. })
  331. t.test('socketPath must be a string', async t => {
  332. const match = /options\.socketPath must be a string/
  333. t.throws(() => ldap.createClient({ socketPath: {} }), match)
  334. t.throws(() => ldap.createClient({ socketPath: [] }), match)
  335. t.throws(() => ldap.createClient({ socketPath: 42 }), match)
  336. })
  337. t.test('cannot supply both url and socketPath', async t => {
  338. t.throws(
  339. () => ldap.createClient({ url: 'foo', socketPath: 'bar' }),
  340. /options\.url \^ options\.socketPath \(String\) required/
  341. )
  342. })
  343. t.test('must supply at least url or socketPath', async t => {
  344. t.throws(
  345. () => ldap.createClient({}),
  346. /options\.url \^ options\.socketPath \(String\) required/
  347. )
  348. })
  349. t.test('exception from bad createClient parameter (issue #418)', t => {
  350. try {
  351. // This port number is totally invalid. It will cause the URL parser
  352. // to throw an exception that should be caught.
  353. ldap.createClient({ url: 'ldap://127.0.0.1:13891389' })
  354. } catch (error) {
  355. t.ok(error)
  356. t.end()
  357. }
  358. })
  359. t.test('url array is correctly assigned', async t => {
  360. getPort().then(function (unusedPortNumber) {
  361. const client = ldap.createClient({
  362. url: [
  363. `ldap://127.0.0.1:${unusedPortNumber}`,
  364. `ldap://127.0.0.2:${unusedPortNumber}`
  365. ],
  366. connectTimeout: 1
  367. })
  368. client.on('connectTimeout', () => {})
  369. client.on('connectError', () => {})
  370. client.on('connectRefused', () => {})
  371. t.equal(client.urls.length, 2)
  372. })
  373. })
  374. // TODO: this test is really flaky. It would be better if we could validate
  375. // the options _withouth_ having to connect to a server.
  376. // t.test('attaches a child function to logger', async t => {
  377. // /* eslint-disable-next-line */
  378. // let client
  379. // const logger = Object.create(require('abstract-logging'))
  380. // const socketPath = getSock()
  381. // const server = ldap.createServer()
  382. // server.listen(socketPath, () => {})
  383. // t.teardown(() => {
  384. // client.unbind(() => server.close())
  385. // })
  386. // client = ldap.createClient({ socketPath, log: logger })
  387. // t.ok(logger.child)
  388. // t.ok(typeof client.log.child === 'function')
  389. // })
  390. t.end()
  391. })
  392. tap.test('simple bind failure', function (t) {
  393. t.context.client.bind(BIND_DN, uuid(), function (err, res) {
  394. t.ok(err)
  395. t.notOk(res)
  396. t.ok(err instanceof ldap.InvalidCredentialsError)
  397. t.ok(err instanceof Error)
  398. t.ok(err.dn)
  399. t.ok(err.message)
  400. t.ok(err.stack)
  401. t.end()
  402. })
  403. })
  404. tap.test('simple bind success', function (t) {
  405. t.context.client.bind(BIND_DN, BIND_PW, function (err, res) {
  406. t.error(err)
  407. t.ok(res)
  408. t.equal(res.status, 0)
  409. t.end()
  410. })
  411. })
  412. tap.test('simple anonymous bind (empty credentials)', function (t) {
  413. t.context.client.bind('', '', function (err, res) {
  414. t.error(err)
  415. t.ok(res)
  416. t.equal(res.status, 0)
  417. t.end()
  418. })
  419. })
  420. tap.test('auto-bind bad credentials', function (t) {
  421. const clt = ldap.createClient({
  422. socketPath: t.context.socketPath,
  423. bindDN: BIND_DN,
  424. bindCredentials: 'totallybogus'
  425. })
  426. clt.once('error', function (err) {
  427. t.equal(err.code, ldap.LDAP_INVALID_CREDENTIALS)
  428. t.ok(clt._socket.destroyed, 'expect socket to be destroyed')
  429. clt.destroy()
  430. t.end()
  431. })
  432. })
  433. tap.test('auto-bind success', function (t) {
  434. const clt = ldap.createClient({
  435. socketPath: t.context.socketPath,
  436. bindDN: BIND_DN,
  437. bindCredentials: BIND_PW
  438. })
  439. clt.once('connect', function () {
  440. t.ok(clt)
  441. clt.destroy()
  442. t.end()
  443. })
  444. })
  445. tap.test('add success', function (t) {
  446. const attrs = [
  447. new Attribute({
  448. type: 'cn',
  449. values: ['test']
  450. })
  451. ]
  452. t.context.client.add('cn=add, ' + SUFFIX, attrs, function (err, res) {
  453. t.error(err)
  454. t.ok(res)
  455. t.equal(res.status, 0)
  456. t.end()
  457. })
  458. })
  459. tap.test('add success with object', function (t) {
  460. const entry = {
  461. cn: ['unit', 'add'],
  462. sn: 'test'
  463. }
  464. t.context.client.add('cn=add, ' + SUFFIX, entry, function (err, res) {
  465. t.error(err)
  466. t.ok(res)
  467. t.equal(res.status, 0)
  468. t.end()
  469. })
  470. })
  471. tap.test('add buffer', function (t) {
  472. const { BerReader } = require('@ldapjs/asn1')
  473. const dn = `cn=add,${SUFFIX}`
  474. const attribute = 'thumbnailPhoto'
  475. const binary = 0xa5
  476. const entry = {
  477. [attribute]: Buffer.from([binary])
  478. }
  479. const write = t.context.client._socket.write
  480. t.context.client._socket.write = (data, encoding, cb) => {
  481. const reader = new BerReader(data)
  482. t.equal(data.byteLength, 48)
  483. t.ok(reader.readSequence())
  484. t.equal(reader.readInt(), 0x1)
  485. t.equal(reader.readSequence(), 0x68)
  486. t.equal(reader.readString(), dn)
  487. t.ok(reader.readSequence())
  488. t.ok(reader.readSequence())
  489. t.equal(reader.readString(), attribute)
  490. t.equal(reader.readSequence(), 0x31)
  491. t.equal(reader.readByte(), 0x4)
  492. t.equal(reader.readByte(), 1)
  493. t.equal(reader.readByte(), binary)
  494. t.context.client._socket.write = write
  495. t.context.client._socket.write(data, encoding, cb)
  496. }
  497. t.context.client.add(dn, entry, function (err, res) {
  498. t.error(err)
  499. t.ok(res)
  500. t.equal(res.status, 0)
  501. t.end()
  502. })
  503. })
  504. tap.test('compare success', function (t) {
  505. t.context.client.compare('cn=compare, ' + SUFFIX, 'cn', 'test', function (err, matched, res) {
  506. t.error(err)
  507. t.ok(matched)
  508. t.ok(res)
  509. t.end()
  510. })
  511. })
  512. tap.test('compare false', function (t) {
  513. t.context.client.compare('cn=compare, ' + SUFFIX, 'cn', 'foo', function (err, matched, res) {
  514. t.error(err)
  515. t.notOk(matched)
  516. t.ok(res)
  517. t.end()
  518. })
  519. })
  520. tap.test('compare bad suffix', function (t) {
  521. t.context.client.compare('cn=' + uuid(), 'cn', 'foo', function (err, matched, res) {
  522. t.ok(err)
  523. t.ok(err instanceof ldap.NoSuchObjectError)
  524. t.notOk(matched)
  525. t.notOk(res)
  526. t.end()
  527. })
  528. })
  529. tap.test('delete success', function (t) {
  530. t.context.client.del('cn=delete, ' + SUFFIX, function (err, res) {
  531. t.error(err)
  532. t.ok(res)
  533. t.end()
  534. })
  535. })
  536. tap.test('delete with control (GH-212)', function (t) {
  537. const control = new ldap.Control({
  538. type: '1.2.3.4',
  539. criticality: false
  540. })
  541. t.context.client.del('cn=delete, ' + SUFFIX, control, function (err, res) {
  542. t.error(err)
  543. t.ok(res)
  544. t.end()
  545. })
  546. })
  547. tap.test('exop success', function (t) {
  548. t.context.client.exop('1.3.6.1.4.1.4203.1.11.3', function (err, value, res) {
  549. t.error(err)
  550. t.ok(value)
  551. t.ok(res)
  552. t.equal(value, 'u:xxyyz@EXAMPLE.NET')
  553. t.end()
  554. })
  555. })
  556. tap.test('exop invalid', function (t) {
  557. t.context.client.exop('1.2.3.4', function (err, res) {
  558. t.ok(err)
  559. t.ok(err instanceof ldap.ProtocolError)
  560. t.notOk(res)
  561. t.end()
  562. })
  563. })
  564. tap.test('bogus exop (GH-17)', function (t) {
  565. t.context.client.exop('cn=root', function (err) {
  566. t.ok(err)
  567. t.end()
  568. })
  569. })
  570. tap.test('modify success', function (t) {
  571. const change = new Change({
  572. type: 'Replace',
  573. modification: new Attribute({
  574. type: 'cn',
  575. values: ['test']
  576. })
  577. })
  578. t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
  579. t.error(err)
  580. t.ok(res)
  581. t.equal(res.status, 0)
  582. t.end()
  583. })
  584. })
  585. // https://github.com/ldapjs/node-ldapjs/pull/435
  586. tap.test('can delete attributes', function (t) {
  587. const change = new Change({
  588. type: 'Delete',
  589. modification: new Attribute({ type: 'cn', values: [null] })
  590. })
  591. t.context.client.modify('cn=modify,' + SUFFIX, change, function (err, res) {
  592. t.error(err)
  593. t.ok(res)
  594. t.equal(res.status, 0)
  595. t.end()
  596. })
  597. })
  598. tap.test('modify array success', function (t) {
  599. const changes = [
  600. new Change({
  601. operation: 'Replace',
  602. modification: new Attribute({
  603. type: 'cn',
  604. values: ['test']
  605. })
  606. }),
  607. new Change({
  608. operation: 'Delete',
  609. modification: new Attribute({
  610. type: 'sn'
  611. })
  612. })
  613. ]
  614. t.context.client.modify('cn=modify, ' + SUFFIX, changes, function (err, res) {
  615. t.error(err)
  616. t.ok(res)
  617. t.equal(res.status, 0)
  618. t.end()
  619. })
  620. })
  621. tap.test('modify DN new RDN only', function (t) {
  622. t.context.client.modifyDN('cn=old, ' + SUFFIX, 'cn=new', function (err, res) {
  623. t.error(err)
  624. t.ok(res)
  625. t.equal(res.status, 0)
  626. t.end()
  627. })
  628. })
  629. tap.test('modify DN new superior', function (t) {
  630. t.context.client.modifyDN('cn=old, ' + SUFFIX, 'cn=new, dc=foo', function (err, res) {
  631. t.error(err)
  632. t.ok(res)
  633. t.equal(res.status, 0)
  634. t.end()
  635. })
  636. })
  637. tap.test('modify DN excessive length (GH-480)', function (t) {
  638. t.context.client.modifyDN('cn=issue-480', 'cn=a292979f2c86d513d48bbb9786b564b3c5228146e5ba46f404724e322544a7304a2b1049168803a5485e2d57a544c6a0d860af91330acb77e5907a9e601ad1227e80e0dc50abe963b47a004f2c90f570450d0e920d15436fdc771e3bdac0487a9735473ed3a79361d1778d7e53a7fb0e5f01f97a75ef05837d1d5496fc86968ff47fcb64', function (err, res) {
  639. t.error(err)
  640. t.ok(res)
  641. t.equal(res.status, 0)
  642. t.end()
  643. })
  644. })
  645. tap.test('modify DN excessive superior length', function (t) {
  646. const { ModifyDnRequest } = messages
  647. const entry = 'cn=Test User,ou=A Long OU ,ou=Another Long OU ,ou=Another Long OU ,dc=acompany,DC=io'
  648. const newSuperior = 'ou=A New Long OU , ou=Another New Long OU , ou=An OU , dc=acompany, dc=io'
  649. const newRdn = entry.replace(/(.*?),.*/, '$1')
  650. const deleteOldRdn = true
  651. const req = new ModifyDnRequest({
  652. entry,
  653. deleteOldRdn,
  654. newRdn,
  655. newSuperior
  656. })
  657. t.equal(req.entry.toString(), 'cn=Test User,ou=A Long OU,ou=Another Long OU,ou=Another Long OU,dc=acompany,DC=io')
  658. t.equal(req.newRdn.toString(), 'cn=Test User')
  659. t.equal(req.deleteOldRdn, true)
  660. t.equal(req.newSuperior.toString(), 'ou=A New Long OU,ou=Another New Long OU,ou=An OU,dc=acompany,dc=io')
  661. t.end()
  662. })
  663. tap.test('search basic', function (t) {
  664. const { SearchResultEntry, SearchResultDone } = messages
  665. t.context.client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) {
  666. t.error(err)
  667. t.ok(res)
  668. let gotEntry = 0
  669. res.on('searchEntry', function (entry) {
  670. t.ok(entry)
  671. t.ok(entry instanceof SearchResultEntry)
  672. t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
  673. t.ok(entry.attributes)
  674. t.ok(entry.attributes.length)
  675. t.equal(entry.attributes[0].type, 'cn')
  676. t.equal(entry.attributes[1].type, 'SN')
  677. gotEntry++
  678. })
  679. res.on('error', function (err) {
  680. t.fail(err)
  681. })
  682. res.on('end', function (res) {
  683. t.ok(res)
  684. t.ok(res instanceof SearchResultDone)
  685. t.equal(res.status, 0)
  686. t.equal(gotEntry, 2)
  687. t.end()
  688. })
  689. })
  690. })
  691. tap.test('search basic with DN', function (t) {
  692. const { SearchResultEntry, SearchResultDone } = messages
  693. t.context.client.search(dn.DN.fromString('cn=test, ' + SUFFIX, '(objectclass=*)'), function (err, res) {
  694. t.error(err)
  695. t.ok(res)
  696. let gotEntry = 0
  697. res.on('searchEntry', function (entry) {
  698. t.ok(entry)
  699. t.ok(entry instanceof SearchResultEntry)
  700. t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
  701. t.ok(entry.attributes)
  702. t.ok(entry.attributes.length)
  703. t.equal(entry.attributes[0].type, 'cn')
  704. t.equal(entry.attributes[1].type, 'SN')
  705. gotEntry++
  706. })
  707. res.on('error', function (err) {
  708. t.fail(err)
  709. })
  710. res.on('end', function (res) {
  711. t.ok(res)
  712. t.ok(res instanceof SearchResultDone)
  713. t.equal(res.status, 0)
  714. t.equal(gotEntry, 2)
  715. t.end()
  716. })
  717. })
  718. })
  719. tap.test('GH-602 search basic with delayed event listener binding', function (t) {
  720. t.context.client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) {
  721. t.error(err)
  722. setTimeout(() => {
  723. let gotEntry = 0
  724. res.on('searchEntry', function () {
  725. gotEntry++
  726. })
  727. res.on('error', function (err) {
  728. t.fail(err)
  729. })
  730. res.on('end', function () {
  731. t.equal(gotEntry, 2)
  732. t.end()
  733. })
  734. }, 100)
  735. })
  736. })
  737. tap.test('search sizeLimit', function (t) {
  738. t.test('over limit', function (t2) {
  739. t.context.client.search('cn=sizelimit', {}, function (err, res) {
  740. t2.error(err)
  741. res.on('error', function (error) {
  742. t2.equal(error.name, 'SizeLimitExceededError')
  743. t2.end()
  744. })
  745. })
  746. })
  747. t.test('under limit', function (t2) {
  748. const limit = 100
  749. t.context.client.search('cn=sizelimit', { sizeLimit: limit }, function (err, res) {
  750. t2.error(err)
  751. let count = 0
  752. res.on('searchEntry', function () {
  753. count++
  754. })
  755. res.on('end', function () {
  756. t2.pass()
  757. t2.equal(count, limit)
  758. t2.end()
  759. })
  760. res.on('error', t2.error.bind(t))
  761. })
  762. })
  763. t.end()
  764. })
  765. tap.test('search paged', { timeout: 10000 }, function (t) {
  766. t.test('paged - no pauses', function (t2) {
  767. let countEntries = 0
  768. let countPages = 0
  769. let currentSearchRequest = null
  770. t.context.client.search('cn=paged', { paged: { pageSize: 100 } }, function (err, res) {
  771. t2.error(err)
  772. res.on('searchEntry', entryListener)
  773. res.on('searchRequest', (searchRequest) => {
  774. t2.ok(searchRequest instanceof SearchRequest)
  775. if (currentSearchRequest === null) {
  776. t2.equal(countPages, 0)
  777. }
  778. currentSearchRequest = searchRequest
  779. })
  780. res.on('page', pageListener)
  781. res.on('error', (err) => t2.error(err))
  782. res.on('end', function (result) {
  783. t2.equal(countEntries, 1000)
  784. t2.equal(countPages, 10)
  785. t2.equal(result.messageId, currentSearchRequest.messageId)
  786. t2.end()
  787. })
  788. t2.teardown(() => {
  789. res.removeListener('searchEntry', entryListener)
  790. res.removeListener('page', pageListener)
  791. })
  792. function entryListener () {
  793. countEntries += 1
  794. }
  795. function pageListener (result) {
  796. countPages += 1
  797. if (countPages < 10) {
  798. t2.equal(result.messageId, currentSearchRequest.messageId)
  799. }
  800. }
  801. })
  802. })
  803. t.test('paged - pauses', function (t2) {
  804. let countPages = 0
  805. t.context.client.search('cn=paged', {
  806. paged: {
  807. pageSize: 100,
  808. pagePause: true
  809. }
  810. }, function (err, res) {
  811. t2.error(err)
  812. res.on('page', pageListener)
  813. res.on('error', (err) => t2.error(err))
  814. res.on('end', function () {
  815. t2.equal(countPages, 9)
  816. t2.end()
  817. })
  818. function pageListener (result, cb) {
  819. countPages++
  820. // cancel after 9 to verify callback usage
  821. if (countPages === 9) {
  822. // another page should never be encountered
  823. res.removeListener('page', pageListener)
  824. .on('page', t2.fail.bind(null, 'unexpected page'))
  825. return cb(new Error())
  826. }
  827. return cb()
  828. }
  829. })
  830. })
  831. t.test('paged - no support (err handled)', function (t2) {
  832. t.context.client.search(SUFFIX, {
  833. paged: { pageSize: 100 }
  834. }, function (err, res) {
  835. t2.error(err)
  836. res.on('pageError', t2.ok.bind(t2))
  837. res.on('end', function () {
  838. t2.pass()
  839. t2.end()
  840. })
  841. })
  842. })
  843. t.test('paged - no support (err not handled)', function (t2) {
  844. t.context.client.search(SUFFIX, {
  845. paged: { pageSize: 100 }
  846. }, function (err, res) {
  847. t2.error(err)
  848. res.on('end', t2.fail.bind(t2))
  849. res.on('error', function (error) {
  850. t2.ok(error)
  851. t2.end()
  852. })
  853. })
  854. })
  855. t.test('paged - redundant control', function (t2) {
  856. try {
  857. t.context.client.search(SUFFIX, {
  858. paged: { pageSize: 100 }
  859. }, new ldap.PagedResultsControl(),
  860. function (err) {
  861. t.error(err)
  862. t2.fail()
  863. })
  864. } catch (e) {
  865. t2.ok(e)
  866. t2.end()
  867. }
  868. })
  869. t.test('paged - handle later error', function (t2) {
  870. let countEntries = 0
  871. let countPages = 0
  872. t.context.client.search('cn=pagederr', {
  873. paged: { pageSize: 1 }
  874. }, function (err, res) {
  875. t2.error(err)
  876. res.on('searchEntry', function () {
  877. t2.ok(++countEntries)
  878. })
  879. res.on('page', function () {
  880. t2.ok(++countPages)
  881. })
  882. res.on('error', function (error) {
  883. t2.ok(error)
  884. t2.equal(countEntries, 1)
  885. t2.equal(countPages, 1)
  886. t2.end()
  887. })
  888. res.on('end', function () {
  889. t2.fail('should not be reached')
  890. })
  891. })
  892. })
  893. tap.test('paged - search with delayed event listener binding', function (t) {
  894. t.context.client.search('cn=paged', { filter: '(objectclass=*)', paged: true }, function (err, res) {
  895. t.error(err)
  896. setTimeout(() => {
  897. let gotEntry = 0
  898. res.on('searchEntry', function () {
  899. gotEntry++
  900. })
  901. res.on('error', function (err) {
  902. t.fail(err)
  903. })
  904. res.on('end', function () {
  905. t.equal(gotEntry, 1000)
  906. t.end()
  907. })
  908. }, 100)
  909. })
  910. })
  911. t.end()
  912. })
  913. // We are skipping the ServerSideSorting test because we have skipped
  914. // properly implementing the controls in order to get v3 shipped. These
  915. // tests should be re-enabled once we have addressed this issue.
  916. // ~ jsumners 2023-02-19
  917. // TODO: re-enable after adding back SSSR support
  918. tap.test('search - sssvlv', { timeout: 10000, skip: true }, function (t) {
  919. t.test('ssv - asc', function (t2) {
  920. let preventry = null
  921. const sssrcontrol = new ldap.ServerSideSortingRequestControl(
  922. {
  923. value: {
  924. attributeType: 'o',
  925. orderingRule: 'caseIgnoreOrderingMatch',
  926. reverseOrder: false
  927. }
  928. }
  929. )
  930. t.context.client.search('cn=sssvlv', {}, sssrcontrol, function (err, res) {
  931. t2.error(err)
  932. res.on('searchEntry', function (entry) {
  933. t2.ok(entry)
  934. t2.ok(entry instanceof ldap.SearchEntry)
  935. t2.ok(entry.attributes)
  936. t2.ok(entry.attributes.length)
  937. if (preventry != null) {
  938. t2.ok(entry.attributes[0]._vals[0] >= preventry.attributes[0]._vals[0])
  939. }
  940. preventry = entry
  941. })
  942. res.on('error', (err) => t2.error(err))
  943. res.on('end', function () {
  944. t2.end()
  945. })
  946. })
  947. })
  948. t.test('ssv - desc', function (t2) {
  949. let preventry = null
  950. const sssrcontrol = new ldap.ServerSideSortingRequestControl(
  951. {
  952. value: {
  953. attributeType: 'o',
  954. orderingRule: 'caseIgnoreOrderingMatch',
  955. reverseOrder: true
  956. }
  957. }
  958. )
  959. t.context.client.search('cn=sssvlv', {}, sssrcontrol, function (err, res) {
  960. t2.error(err)
  961. res.on('searchEntry', function (entry) {
  962. t2.ok(entry)
  963. t2.ok(entry instanceof ldap.SearchEntry)
  964. t2.ok(entry.attributes)
  965. t2.ok(entry.attributes.length)
  966. if (preventry != null) {
  967. t2.ok(entry.attributes[0]._vals[0] <= preventry.attributes[0]._vals[0])
  968. }
  969. preventry = entry
  970. })
  971. res.on('error', (err) => t2.error(err))
  972. res.on('end', function () {
  973. t2.end()
  974. })
  975. })
  976. })
  977. t.test('vlv - first page', { skip: true }, function (t2) {
  978. // This test is disabled.
  979. // See https://github.com/ldapjs/node-ldapjs/pull/797#issuecomment-1094132289
  980. const sssrcontrol = new ldap.ServerSideSortingRequestControl(
  981. {
  982. value: {
  983. attributeType: 'o',
  984. orderingRule: 'caseIgnoreOrderingMatch',
  985. reverseOrder: false
  986. }
  987. }
  988. )
  989. const vlvrcontrol = new ldap.VirtualListViewRequestControl(
  990. {
  991. value: {
  992. beforeCount: 0,
  993. afterCount: 9,
  994. targetOffset: 1,
  995. contentCount: 0
  996. }
  997. }
  998. )
  999. let count = 0
  1000. let preventry = null
  1001. t.context.client.search('cn=sssvlv', {}, [sssrcontrol, vlvrcontrol], function (err, res) {
  1002. t2.error(err)
  1003. res.on('searchEntry', function (entry) {
  1004. t2.ok(entry)
  1005. t2.ok(entry instanceof ldap.SearchEntry)
  1006. t2.ok(entry.attributes)
  1007. t2.ok(entry.attributes.length)
  1008. if (preventry != null) {
  1009. t2.ok(entry.attributes[0]._vals[0] >= preventry.attributes[0]._vals[0])
  1010. }
  1011. preventry = entry
  1012. count++
  1013. })
  1014. res.on('error', (err) => t2.error(err))
  1015. res.on('end', function () {
  1016. t2.equal(count, 10)
  1017. t2.end()
  1018. })
  1019. })
  1020. })
  1021. t.test('vlv - last page', { skip: true }, function (t2) {
  1022. // This test is disabled.
  1023. // See https://github.com/ldapjs/node-ldapjs/pull/797#issuecomment-1094132289
  1024. const sssrcontrol = new ldap.ServerSideSortingRequestControl(
  1025. {
  1026. value: {
  1027. attributeType: 'o',
  1028. orderingRule: 'caseIgnoreOrderingMatch',
  1029. reverseOrder: false
  1030. }
  1031. }
  1032. )
  1033. const vlvrcontrol = new ldap.VirtualListViewRequestControl(
  1034. {
  1035. value: {
  1036. beforeCount: 0,
  1037. afterCount: 9,
  1038. targetOffset: 91,
  1039. contentCount: 0
  1040. }
  1041. }
  1042. )
  1043. let count = 0
  1044. let preventry = null
  1045. t.context.client.search('cn=sssvlv', {}, [sssrcontrol, vlvrcontrol], function (err, res) {
  1046. t2.error(err)
  1047. res.on('searchEntry', function (entry) {
  1048. t2.ok(entry)
  1049. t2.ok(entry instanceof ldap.SearchEntry)
  1050. t2.ok(entry.attributes)
  1051. t2.ok(entry.attributes.length)
  1052. if (preventry != null) {
  1053. t2.ok(entry.attributes[0]._vals[0] >= preventry.attributes[0]._vals[0])
  1054. }
  1055. preventry = entry
  1056. count++
  1057. })
  1058. res.on('error', (err) => t2.error(err))
  1059. res.on('end', function () {
  1060. t2.equal(count, 10)
  1061. t2.end()
  1062. })
  1063. })
  1064. })
  1065. t.end()
  1066. })
  1067. tap.test('search referral', function (t) {
  1068. t.context.client.search('cn=ref, ' + SUFFIX, '(objectclass=*)', function (err, res) {
  1069. t.error(err)
  1070. t.ok(res)
  1071. let gotEntry = 0
  1072. let gotReferral = false
  1073. res.on('searchEntry', function () {
  1074. gotEntry++
  1075. })
  1076. res.on('searchReference', function (referral) {
  1077. gotReferral = true
  1078. t.ok(referral)
  1079. t.ok(referral instanceof SearchResultReference)
  1080. t.ok(referral.uris)
  1081. t.ok(referral.uris.length)
  1082. })
  1083. res.on('error', function (err) {
  1084. t.fail(err)
  1085. })
  1086. res.on('end', function (res) {
  1087. t.ok(res)
  1088. t.ok(res instanceof SearchResultDone)
  1089. t.equal(res.status, 0)
  1090. t.equal(gotEntry, 0)
  1091. t.ok(gotReferral)
  1092. t.end()
  1093. })
  1094. })
  1095. })
  1096. tap.test('search rootDSE', function (t) {
  1097. t.context.client.search('', '(objectclass=*)', function (err, res) {
  1098. t.error(err)
  1099. t.ok(res)
  1100. res.on('searchEntry', function (entry) {
  1101. t.ok(entry)
  1102. t.equal(entry.dn.toString(), '')
  1103. t.ok(entry.attributes)
  1104. })
  1105. res.on('error', function (err) {
  1106. t.fail(err)
  1107. })
  1108. res.on('end', function (res) {
  1109. t.ok(res)
  1110. t.ok(res instanceof SearchResultDone)
  1111. t.equal(res.status, 0)
  1112. t.end()
  1113. })
  1114. })
  1115. })
  1116. tap.test('search empty attribute', function (t) {
  1117. t.context.client.search('dc=empty', '(objectclass=*)', function (err, res) {
  1118. t.error(err)
  1119. t.ok(res)
  1120. let gotEntry = 0
  1121. res.on('searchEntry', function (entry) {
  1122. const obj = entry.pojo
  1123. t.equal('dc=empty', obj.objectName)
  1124. const member = entry.attributes[0]
  1125. t.ok(member)
  1126. t.equal(member.values.length, 0)
  1127. const rangedMember = entry.attributes[1]
  1128. t.equal(rangedMember.type, 'member;range=0-1')
  1129. t.equal(rangedMember.values.length, 2)
  1130. gotEntry++
  1131. })
  1132. res.on('error', function (err) {
  1133. t.fail(err)
  1134. })
  1135. res.on('end', function (res) {
  1136. t.ok(res)
  1137. t.ok(res instanceof SearchResultDone)
  1138. t.equal(res.status, 0)
  1139. t.equal(gotEntry, 1)
  1140. t.end()
  1141. })
  1142. })
  1143. })
  1144. tap.test('GH-21 binary attributes', function (t) {
  1145. t.context.client.search('cn=bin, ' + SUFFIX, '(objectclass=*)', function (err, res) {
  1146. t.error(err)
  1147. t.ok(res)
  1148. let gotEntry = 0
  1149. const expect = Buffer.from('\u00bd + \u00bc = \u00be', 'utf8')
  1150. const expect2 = Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA])
  1151. res.on('searchEntry', function (entry) {
  1152. t.ok(entry)
  1153. t.ok(entry instanceof SearchResultEntry)
  1154. t.equal(entry.dn.toString(), 'cn=bin,' + SUFFIX)
  1155. t.ok(entry.attributes)
  1156. t.ok(entry.attributes.length)
  1157. t.equal(entry.attributes[0].type, 'foo;binary')
  1158. t.equal(entry.attributes[0].values[0], expect.toString('base64'))
  1159. t.equal(entry.attributes[0].buffers[0].toString('base64'),
  1160. expect.toString('base64'))
  1161. t.ok(entry.attributes[1].type, 'gb18030')
  1162. t.equal(entry.attributes[1].buffers.length, 1)
  1163. t.equal(expect2.length, entry.attributes[1].buffers[0].length)
  1164. for (let i = 0; i < expect2.length; i++) { t.equal(expect2[i], entry.attributes[1].buffers[0][i]) }
  1165. gotEntry++
  1166. })
  1167. res.on('error', function (err) {
  1168. t.fail(err)
  1169. })
  1170. res.on('end', function (res) {
  1171. t.ok(res)
  1172. t.ok(res instanceof SearchResultDone)
  1173. t.equal(res.status, 0)
  1174. t.equal(gotEntry, 1)
  1175. t.end()
  1176. })
  1177. })
  1178. })
  1179. tap.test('GH-23 case insensitive attribute filtering', function (t) {
  1180. const opts = {
  1181. filter: '(objectclass=*)',
  1182. attributes: ['Cn']
  1183. }
  1184. t.context.client.search('cn=test, ' + SUFFIX, opts, function (err, res) {
  1185. t.error(err)
  1186. t.ok(res)
  1187. let gotEntry = 0
  1188. res.on('searchEntry', function (entry) {
  1189. t.ok(entry)
  1190. t.ok(entry instanceof SearchResultEntry)
  1191. t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
  1192. t.ok(entry.attributes)
  1193. t.ok(entry.attributes.length)
  1194. t.equal(entry.attributes[0].type, 'cn')
  1195. gotEntry++
  1196. })
  1197. res.on('error', function (err) {
  1198. t.fail(err)
  1199. })
  1200. res.on('end', function (res) {
  1201. t.ok(res)
  1202. t.ok(res instanceof SearchResultDone)
  1203. t.equal(res.status, 0)
  1204. t.equal(gotEntry, 2)
  1205. t.end()
  1206. })
  1207. })
  1208. })
  1209. tap.test('GH-24 attribute selection of *', function (t) {
  1210. const opts = {
  1211. filter: '(objectclass=*)',
  1212. attributes: ['*']
  1213. }
  1214. t.context.client.search('cn=test, ' + SUFFIX, opts, function (err, res) {
  1215. t.error(err)
  1216. t.ok(res)
  1217. let gotEntry = 0
  1218. res.on('searchEntry', function (entry) {
  1219. t.ok(entry)
  1220. t.ok(entry instanceof SearchResultEntry)
  1221. t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
  1222. t.ok(entry.attributes)
  1223. t.ok(entry.attributes.length)
  1224. t.equal(entry.attributes[0].type, 'cn')
  1225. t.equal(entry.attributes[1].type, 'SN')
  1226. gotEntry++
  1227. })
  1228. res.on('error', function (err) {
  1229. t.fail(err)
  1230. })
  1231. res.on('end', function (res) {
  1232. t.ok(res)
  1233. t.ok(res instanceof SearchResultDone)
  1234. t.equal(res.status, 0)
  1235. t.equal(gotEntry, 2)
  1236. t.end()
  1237. })
  1238. })
  1239. })
  1240. tap.test('idle timeout', function (t) {
  1241. t.context.client.idleTimeout = 250
  1242. function premature () {
  1243. t.error(true)
  1244. }
  1245. t.context.client.on('idle', premature)
  1246. t.context.client.search('dc=slow', 'objectclass=*', function (err, res) {
  1247. t.error(err)
  1248. res.on('searchEntry', function (res) {
  1249. t.ok(res)
  1250. })
  1251. res.on('error', function (err) {
  1252. t.error(err)
  1253. })
  1254. res.on('end', function () {
  1255. const late = setTimeout(function () {
  1256. t.fail('too late')
  1257. }, 500)
  1258. // It's ok to go idle now
  1259. t.context.client.removeListener('idle', premature)
  1260. t.context.client.on('idle', function () {
  1261. clearTimeout(late)
  1262. t.context.client.removeAllListeners('idle')
  1263. t.context.client.idleTimeout = 0
  1264. t.end()
  1265. })
  1266. })
  1267. })
  1268. })
  1269. tap.test('setup action', function (t) {
  1270. const setupClient = ldap.createClient({
  1271. connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10),
  1272. socketPath: t.context.socketPath
  1273. })
  1274. setupClient.on('setup', function (clt, cb) {
  1275. clt.bind(BIND_DN, BIND_PW, function (err) {
  1276. t.error(err)
  1277. cb(err)
  1278. })
  1279. })
  1280. setupClient.search(SUFFIX, { scope: 'base' }, function (err, res) {
  1281. t.error(err)
  1282. t.ok(res)
  1283. res.on('end', function () {
  1284. setupClient.destroy()
  1285. t.end()
  1286. })
  1287. })
  1288. })
  1289. tap.test('setup reconnect', function (t) {
  1290. const rClient = ldap.createClient({
  1291. connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10),
  1292. socketPath: t.context.socketPath,
  1293. reconnect: true
  1294. })
  1295. rClient.on('setup', function (clt, cb) {
  1296. clt.bind(BIND_DN, BIND_PW, function (err) {
  1297. t.error(err)
  1298. cb(err)
  1299. })
  1300. })
  1301. function doSearch (_, cb) {
  1302. rClient.search(SUFFIX, { scope: 'base' }, function (err, res) {
  1303. t.error(err)
  1304. res.on('end', function () {
  1305. cb()
  1306. })
  1307. })
  1308. }
  1309. vasync.pipeline({
  1310. funcs: [
  1311. doSearch,
  1312. function cleanDisconnect (_, cb) {
  1313. t.ok(rClient.connected)
  1314. rClient.once('close', function (err) {
  1315. t.error(err)
  1316. t.equal(rClient.connected, false)
  1317. cb()
  1318. })
  1319. rClient.unbind()
  1320. },
  1321. doSearch,
  1322. function simulateError (_, cb) {
  1323. const msg = 'fake socket error'
  1324. rClient.once('error', function (err) {
  1325. t.equal(err.message, msg)
  1326. t.ok(err)
  1327. })
  1328. rClient.once('close', function () {
  1329. // can't test had_err because the socket error is being faked
  1330. cb()
  1331. })
  1332. rClient._socket.emit('error', new Error(msg))
  1333. },
  1334. doSearch
  1335. ]
  1336. }, function (err) {
  1337. t.error(err)
  1338. rClient.destroy()
  1339. t.end()
  1340. })
  1341. })
  1342. tap.test('setup abort', function (t) {
  1343. const setupClient = ldap.createClient({
  1344. connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10),
  1345. socketPath: t.context.socketPath,
  1346. reconnect: true
  1347. })
  1348. const message = "It's a trap!"
  1349. setupClient.on('setup', function (clt, cb) {
  1350. // simulate failure
  1351. t.ok(clt)
  1352. cb(new Error(message))
  1353. })
  1354. setupClient.on('setupError', function (err) {
  1355. t.ok(true)
  1356. t.equal(err.message, message)
  1357. setupClient.destroy()
  1358. t.end()
  1359. })
  1360. })
  1361. tap.test('abort reconnect', function (t) {
  1362. const abortClient = ldap.createClient({
  1363. connectTimeout: parseInt(LDAP_CONNECT_TIMEOUT, 10),
  1364. socketPath: 'an invalid path',
  1365. reconnect: true
  1366. })
  1367. let retryCount = 0
  1368. abortClient.on('connectError', function () {
  1369. ++retryCount
  1370. })
  1371. abortClient.once('connectError', function () {
  1372. t.ok(true)
  1373. abortClient.once('destroy', function () {
  1374. t.ok(retryCount < 3)
  1375. t.end()
  1376. })
  1377. abortClient.destroy()
  1378. })
  1379. })
  1380. tap.test('reconnect max retries', function (t) {
  1381. const RETRIES = 5
  1382. const rClient = ldap.createClient({
  1383. connectTimeout: 100,
  1384. socketPath: 'an invalid path',
  1385. reconnect: {
  1386. failAfter: RETRIES,
  1387. // Keep the test duration low
  1388. initialDelay: 10,
  1389. maxDelay: 100
  1390. }
  1391. })
  1392. let count = 0
  1393. rClient.on('connectError', function () {
  1394. count++
  1395. })
  1396. rClient.on('error', function (err) {
  1397. t.ok(err)
  1398. t.equal(count, RETRIES)
  1399. rClient.destroy()
  1400. t.end()
  1401. })
  1402. })
  1403. tap.test('reconnect on server close', function (t) {
  1404. const clt = ldap.createClient({
  1405. socketPath: t.context.socketPath,
  1406. reconnect: true
  1407. })
  1408. clt.on('setup', function (sclt, cb) {
  1409. sclt.bind(BIND_DN, BIND_PW, function (err) {
  1410. t.error(err)
  1411. cb(err)
  1412. })
  1413. })
  1414. clt.once('connect', function () {
  1415. t.ok(clt._socket)
  1416. clt.once('connect', function () {
  1417. t.ok(true, 'successful reconnect')
  1418. clt.destroy()
  1419. t.end()
  1420. })
  1421. // Simulate server-side close
  1422. clt._socket.destroy()
  1423. })
  1424. })
  1425. tap.test('no auto-reconnect on unbind', function (t) {
  1426. const clt = ldap.createClient({
  1427. socketPath: t.context.socketPath,
  1428. reconnect: true
  1429. })
  1430. clt.on('setup', function (sclt, cb) {
  1431. sclt.bind(BIND_DN, BIND_PW, function (err) {
  1432. t.error(err)
  1433. cb(err)
  1434. })
  1435. })
  1436. clt.once('connect', function () {
  1437. clt.once('connect', function () {
  1438. t.error(new Error('client should not reconnect'))
  1439. })
  1440. clt.once('close', function () {
  1441. t.ok(true, 'initial close')
  1442. setImmediate(function () {
  1443. t.ok(!clt.connected, 'should not be connected')
  1444. t.ok(!clt.connecting, 'should not be connecting')
  1445. clt.destroy()
  1446. t.end()
  1447. })
  1448. })
  1449. clt.unbind()
  1450. })
  1451. })
  1452. tap.test('abandon (GH-27)', function (t) {
  1453. // FIXME: test abandoning a real request
  1454. t.context.client.abandon(401876543, function (err) {
  1455. t.error(err)
  1456. t.end()
  1457. })
  1458. })
  1459. tap.test('search timeout (GH-51)', function (t) {
  1460. t.context.client.timeout = 250
  1461. t.context.client.search('dc=timeout', 'objectclass=*', function (err, res) {
  1462. t.error(err)
  1463. res.on('error', function () {
  1464. t.end()
  1465. })
  1466. })
  1467. })
  1468. tap.test('resultError handling', function (t) {
  1469. const client = t.context.client
  1470. vasync.pipeline({ funcs: [errSearch, cleanSearch] }, function (err) {
  1471. t.error(err)
  1472. client.removeListener('resultError', error1)
  1473. client.removeListener('resultError', error2)
  1474. t.end()
  1475. })
  1476. function errSearch (_, cb) {
  1477. client.once('resultError', error1)
  1478. client.search('cn=busy', {}, function (err, res) {
  1479. t.error(err)
  1480. res.once('error', function (error) {
  1481. t.equal(error.name, 'BusyError')
  1482. cb()
  1483. })
  1484. })
  1485. }
  1486. function cleanSearch (_, cb) {
  1487. client.on('resultError', error2)
  1488. client.search(SUFFIX, {}, function (err, res) {
  1489. t.error(err)
  1490. res.once('end', function () {
  1491. t.pass()
  1492. cb()
  1493. })
  1494. })
  1495. }
  1496. function error1 (error) {
  1497. t.equal(error.name, 'BusyError')
  1498. }
  1499. function error2 () {
  1500. t.fail('should not get error')
  1501. }
  1502. })
  1503. tap.test('connection refused', function (t) {
  1504. getPort().then(function (unusedPortNumber) {
  1505. const client = ldap.createClient({
  1506. url: `ldap://0.0.0.0:${unusedPortNumber}`
  1507. })
  1508. client.on('connectRefused', () => {})
  1509. client.bind('cn=root', 'secret', function (err, res) {
  1510. t.ok(err)
  1511. t.type(err, Error)
  1512. t.equal(err.code, 'ECONNREFUSED')
  1513. t.notOk(res)
  1514. t.end()
  1515. })
  1516. })
  1517. })
  1518. tap.test('connection timeout', function (t) {
  1519. getPort().then(function (unusedPortNumber) {
  1520. const client = ldap.createClient({
  1521. url: `ldap://example.org:${unusedPortNumber}`,
  1522. connectTimeout: 1,
  1523. timeout: 1
  1524. })
  1525. client.on('connectTimeout', () => {})
  1526. let done = false
  1527. setTimeout(function () {
  1528. if (!done) {
  1529. throw new Error('LDAPJS waited for the server for too long')
  1530. }
  1531. }, 2000)
  1532. client.bind('cn=root', 'secret', function (err, res) {
  1533. t.ok(err)
  1534. t.type(err, Error)
  1535. t.equal(err.message, 'connection timeout')
  1536. done = true
  1537. t.notOk(res)
  1538. t.end()
  1539. })
  1540. })
  1541. })
  1542. tap.test('emitError', function (t) {
  1543. t.test('connectTimeout', function (t) {
  1544. getPort().then(function (unusedPortNumber) {
  1545. const client = ldap.createClient({
  1546. url: `ldap://example.org:${unusedPortNumber}`,
  1547. connectTimeout: 1,
  1548. timeout: 1
  1549. })
  1550. const timeout = setTimeout(function () {
  1551. throw new Error('LDAPJS waited for the server for too long')
  1552. }, 2000)
  1553. client.on('error', (err) => {
  1554. t.fail(err)
  1555. })
  1556. client.on('connectTimeout', (err) => {
  1557. t.ok(err)
  1558. t.type(err, Error)
  1559. t.equal(err.message, 'connection timeout')
  1560. clearTimeout(timeout)
  1561. t.end()
  1562. })
  1563. client.bind('cn=root', 'secret', () => {})
  1564. })
  1565. })
  1566. t.test('connectTimeout to error', function (t) {
  1567. getPort().then(function (unusedPortNumber) {
  1568. const client = ldap.createClient({
  1569. url: `ldap://example.org:${unusedPortNumber}`,
  1570. connectTimeout: 1,
  1571. timeout: 1
  1572. })
  1573. const timeout = setTimeout(function () {
  1574. throw new Error('LDAPJS waited for the server for too long')
  1575. }, 2000)
  1576. client.on('error', (err) => {
  1577. t.ok(err)
  1578. t.type(err, Error)
  1579. t.equal(err.message, 'connectTimeout: connection timeout')
  1580. clearTimeout(timeout)
  1581. t.end()
  1582. })
  1583. client.bind('cn=root', 'secret', () => {})
  1584. })
  1585. })
  1586. t.test('connectRefused', function (t) {
  1587. getPort().then(function (unusedPortNumber) {
  1588. const client = ldap.createClient({
  1589. url: `ldap://0.0.0.0:${unusedPortNumber}`
  1590. })
  1591. client.on('error', (err) => {
  1592. t.fail(err)
  1593. })
  1594. client.on('connectRefused', (err) => {
  1595. t.ok(err)
  1596. t.type(err, Error)
  1597. t.equal(err.message, `connect ECONNREFUSED 0.0.0.0:${unusedPortNumber}`)
  1598. t.equal(err.code, 'ECONNREFUSED')
  1599. t.end()
  1600. })
  1601. client.bind('cn=root', 'secret', () => {})
  1602. })
  1603. })
  1604. t.test('connectRefused to error', function (t) {
  1605. getPort().then(function (unusedPortNumber) {
  1606. const client = ldap.createClient({
  1607. url: `ldap://0.0.0.0:${unusedPortNumber}`
  1608. })
  1609. client.on('error', (err) => {
  1610. t.ok(err)
  1611. t.type(err, Error)
  1612. t.equal(err.message, `connectRefused: connect ECONNREFUSED 0.0.0.0:${unusedPortNumber}`)
  1613. t.equal(err.code, 'ECONNREFUSED')
  1614. t.end()
  1615. })
  1616. client.bind('cn=root', 'secret', () => {})
  1617. })
  1618. })
  1619. t.end()
  1620. })
  1621. tap.test('socket destroy', function (t) {
  1622. const clt = ldap.createClient({
  1623. socketPath: t.context.socketPath,
  1624. bindDN: BIND_DN,
  1625. bindCredentials: BIND_PW
  1626. })
  1627. clt.once('connect', function () {
  1628. t.ok(clt)
  1629. clt._socket.once('close', function () {
  1630. t.ok(!clt.connected)
  1631. t.end()
  1632. })
  1633. clt.destroy()
  1634. })
  1635. clt.once('destroy', function () {
  1636. t.ok(clt.destroyed)
  1637. })
  1638. })