ldap-message.test.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. 'use strict'
  2. const tap = require('tap')
  3. const { BerReader } = require('@ldapjs/asn1')
  4. const warning = require('./deprecations')
  5. const { Control } = require('@ldapjs/controls')
  6. const LdapMessage = require('./ldap-message')
  7. // Silence the standard warning logs. We will test the messages explicitly.
  8. process.removeAllListeners('warning')
  9. const {
  10. abandonRequestBytes,
  11. bindRequestBytes,
  12. deleteRequestBytes
  13. } = require('./messages/_fixtures/message-byte-arrays')
  14. tap.test('constructor', t => {
  15. t.test('no args', async t => {
  16. const message = new LdapMessage()
  17. t.strictSame(message.pojo, {
  18. messageId: 1,
  19. protocolOp: undefined,
  20. type: 'LdapMessage',
  21. controls: []
  22. })
  23. })
  24. t.test('all options supplied', t => {
  25. process.on('warning', handler)
  26. t.teardown(async () => {
  27. process.removeListener('warning', handler)
  28. warning.emitted.set('LDAP_MESSAGE_DEP_001', false)
  29. })
  30. const message = new LdapMessage({
  31. messageID: 10,
  32. protocolOp: 0x01,
  33. controls: [new Control({ type: 'foo', value: 'foo' })]
  34. })
  35. t.strictSame(message.pojo, {
  36. messageId: 10,
  37. protocolOp: 0x01,
  38. type: 'LdapMessage',
  39. controls: [{
  40. type: 'foo',
  41. value: 'foo',
  42. criticality: false
  43. }]
  44. })
  45. function handler (error) {
  46. t.equal(error.message, 'messageID is deprecated. Use messageId instead.')
  47. t.end()
  48. }
  49. })
  50. t.end()
  51. })
  52. tap.test('misc', t => {
  53. t.test('toStringTag is correct', async t => {
  54. const message = new LdapMessage()
  55. t.equal(Object.prototype.toString.call(message), '[object LdapMessage]')
  56. })
  57. t.test('dn returns _dn', async t => {
  58. class Foo extends LdapMessage {
  59. get _dn () {
  60. return 'foo'
  61. }
  62. }
  63. const message = new Foo()
  64. t.equal(message.dn, 'foo')
  65. })
  66. t.test('protocolOp returns code', async t => {
  67. const message = new LdapMessage({ protocolOp: 1 })
  68. t.equal(message.protocolOp, 1)
  69. })
  70. t.test('json emits warning', t => {
  71. process.on('warning', handler)
  72. t.teardown(async () => {
  73. process.removeListener('warning', handler)
  74. warning.emitted.set('LDAP_MESSAGE_DEP_002', false)
  75. })
  76. const message = new LdapMessage()
  77. t.ok(message.json)
  78. function handler (error) {
  79. t.equal(
  80. error.message,
  81. 'The .json property is deprecated. Use .pojo instead.'
  82. )
  83. t.end()
  84. }
  85. })
  86. t.test('toString returns JSON', async t => {
  87. const message = new LdapMessage()
  88. const expected = JSON.stringify(message.pojo)
  89. t.equal(message.toString(), expected)
  90. })
  91. t.end()
  92. })
  93. tap.test('.controls', t => {
  94. t.test('sets/gets', async t => {
  95. const req = new LdapMessage()
  96. t.strictSame(req.controls, [])
  97. req.controls = [new Control()]
  98. t.strictSame(req.pojo, {
  99. messageId: 1,
  100. protocolOp: undefined,
  101. type: 'LdapMessage',
  102. controls: [{
  103. type: '',
  104. value: null,
  105. criticality: false
  106. }]
  107. })
  108. })
  109. t.test('rejects for non-array', async t => {
  110. const req = new LdapMessage()
  111. t.throws(
  112. () => {
  113. req.controls = {}
  114. },
  115. 'controls must be an array'
  116. )
  117. })
  118. t.test('rejects if array item is not a control', async t => {
  119. const req = new LdapMessage()
  120. t.throws(
  121. () => {
  122. req.controls = ['foo']
  123. },
  124. 'control must be an instance of LdapControl'
  125. )
  126. })
  127. t.end()
  128. })
  129. tap.test('.id', t => {
  130. t.test('sets/gets', async t => {
  131. const req = new LdapMessage()
  132. t.equal(req.id, 1)
  133. req.id = 2
  134. t.equal(req.id, 2)
  135. t.equal(req.messageId, 2)
  136. req.messageId = 3
  137. t.equal(req.id, 3)
  138. })
  139. t.test('throws if not an integer', async t => {
  140. const req = new LdapMessage()
  141. t.throws(
  142. () => {
  143. req.id = 1.5
  144. },
  145. 'id must be an integer'
  146. )
  147. })
  148. t.test('get messageID is deprecated', t => {
  149. process.on('warning', handler)
  150. t.teardown(async () => {
  151. process.removeListener('warning', handler)
  152. warning.emitted.set('LDAP_MESSAGE_DEP_001', false)
  153. })
  154. const message = new LdapMessage()
  155. t.ok(message.messageID)
  156. function handler (error) {
  157. t.equal(
  158. error.message,
  159. 'messageID is deprecated. Use messageId instead.'
  160. )
  161. t.end()
  162. }
  163. })
  164. t.test('set messageID is deprecated', t => {
  165. process.on('warning', handler)
  166. t.teardown(async () => {
  167. process.removeListener('warning', handler)
  168. warning.emitted.set('LDAP_MESSAGE_DEP_001', false)
  169. })
  170. const message = new LdapMessage()
  171. message.messageID = 2
  172. function handler (error) {
  173. t.equal(
  174. error.message,
  175. 'messageID is deprecated. Use messageId instead.'
  176. )
  177. t.end()
  178. }
  179. })
  180. t.end()
  181. })
  182. tap.test('toBer', t => {
  183. t.test('throws for bad subclass', async t => {
  184. class Foo extends LdapMessage {
  185. }
  186. const message = new Foo()
  187. t.throws(
  188. () => message.toBer(),
  189. Error('LdapMessage does not implement _toBer')
  190. )
  191. })
  192. t.test('converts BindRequest to BER', async t => {
  193. const reqBuffer = Buffer.from(bindRequestBytes)
  194. const reader = new BerReader(reqBuffer)
  195. const message = LdapMessage.parse(reader)
  196. const ber = message.toBer()
  197. t.equal('[object BerReader]', Object.prototype.toString.call(ber))
  198. t.equal(reqBuffer.compare(ber.buffer), 0)
  199. })
  200. t.test('converts DeleteRequest to BER', async t => {
  201. const reqBuffer = Buffer.from(deleteRequestBytes)
  202. const reader = new BerReader(reqBuffer)
  203. const message = LdapMessage.parse(reader)
  204. const ber = message.toBer()
  205. t.equal(reqBuffer.compare(ber.buffer), 0)
  206. })
  207. t.end()
  208. })
  209. tap.test('#parse', t => {
  210. t.test('parses an abandon request', async t => {
  211. const reader = new BerReader(Buffer.from(abandonRequestBytes))
  212. const message = LdapMessage.parse(reader)
  213. t.strictSame(message.pojo, {
  214. messageId: 6,
  215. protocolOp: 0x50,
  216. type: 'AbandonRequest',
  217. abandonId: 5,
  218. controls: []
  219. })
  220. })
  221. t.test('parses a bind request', async t => {
  222. const reader = new BerReader(Buffer.from(bindRequestBytes))
  223. const message = LdapMessage.parse(reader)
  224. t.strictSame(message.pojo, {
  225. messageId: 1,
  226. protocolOp: 0x60,
  227. type: 'BindRequest',
  228. version: 3,
  229. name: 'uid=admin,ou=system',
  230. authenticationType: 'simple',
  231. credentials: 'secret',
  232. controls: []
  233. })
  234. t.equal(message.name, 'uid=admin,ou=system')
  235. })
  236. t.test('parses a delete request with controls', async t => {
  237. const reader = new BerReader(Buffer.from(deleteRequestBytes))
  238. const message = LdapMessage.parse(reader)
  239. // We need to parse the JSON representation because stringSame will return
  240. // false when comparing a plain object to an instance of Control.
  241. t.strictSame(JSON.parse(message.toString()), {
  242. messageId: 5,
  243. protocolOp: 0x4a,
  244. type: 'DeleteRequest',
  245. entry: 'dc=example,dc=com',
  246. controls: [{
  247. type: '1.2.840.113556.1.4.805',
  248. criticality: true,
  249. value: null
  250. }]
  251. })
  252. })
  253. t.end()
  254. })
  255. tap.test('#parseToPojo', t => {
  256. t.test('throws because not implemented', async t => {
  257. const expected = Error('Use LdapMessage.parse, or a specific message type\'s parseToPojo, instead.')
  258. t.throws(
  259. () => LdapMessage.parseToPojo(),
  260. expected
  261. )
  262. })
  263. t.end()
  264. })