ldap-message.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. 'use strict'
  2. const { BerReader, BerWriter } = require('@ldapjs/asn1')
  3. const warning = require('./deprecations')
  4. /**
  5. * Implements a base LDAP message as defined in
  6. * https://www.rfc-editor.org/rfc/rfc4511.html#section-4.1.1.
  7. */
  8. class LdapMessage {
  9. #messageId = 0
  10. #protocolOp
  11. #controls = []
  12. /**
  13. * @typedef {object} LdapMessageOptions
  14. * @property {number} [messageId=1] An identifier for the message.
  15. * @property {number} [protocolOp] The tag for the message operation.
  16. * @property {import('@ldapjs/controls').Control[]} [controls] A set of LDAP
  17. * controls to send with the message. See the `@ldapjs/controls` package.
  18. */
  19. /**
  20. * @param {LdapMessageOptions} [options]
  21. */
  22. constructor (options = {}) {
  23. this.#messageId = parseInt(options.messageId ?? options.messageID ?? '1', 10)
  24. if (options.messageID !== undefined) {
  25. warning.emit('LDAP_MESSAGE_DEP_001')
  26. }
  27. if (typeof options.protocolOp === 'number') {
  28. this.#protocolOp = options.protocolOp
  29. }
  30. this.controls = options.controls ?? []
  31. }
  32. get [Symbol.toStringTag] () {
  33. return 'LdapMessage'
  34. }
  35. /**
  36. * A copy of the list of controls that will be sent with the request.
  37. *
  38. * @returns {import('@ldapjs/controls').Control[]}
  39. */
  40. get controls () {
  41. return this.#controls.slice(0)
  42. }
  43. /**
  44. * Define the list of controls that will be sent with the request. Any
  45. * existing controls will be discarded.
  46. *
  47. * @param {import('@ldapjs/controls').Control[]} values
  48. *
  49. * @throws When a control value is invalid.
  50. */
  51. set controls (values) {
  52. if (Array.isArray(values) !== true) {
  53. throw Error('controls must be an array')
  54. }
  55. const newControls = []
  56. for (const val of values) {
  57. if (Object.prototype.toString.call(val) !== '[object LdapControl]') {
  58. throw Error('control must be an instance of LdapControl')
  59. }
  60. newControls.push(val)
  61. }
  62. this.#controls = newControls
  63. }
  64. /**
  65. * The message identifier.
  66. *
  67. * @type {number}
  68. */
  69. get id () {
  70. return this.#messageId
  71. }
  72. /**
  73. * Define the message identifier for the request.
  74. *
  75. * @param {number} value
  76. */
  77. set id (value) {
  78. if (Number.isInteger(value) === false) {
  79. throw Error('id must be an integer')
  80. }
  81. this.#messageId = value
  82. }
  83. /**
  84. * Alias for {@link id}.
  85. *
  86. * @returns {number}
  87. */
  88. get messageId () {
  89. return this.id
  90. }
  91. /**
  92. * Alias for {@link id}.
  93. *
  94. * @param {number} value
  95. */
  96. set messageId (value) {
  97. this.id = value
  98. }
  99. /**
  100. * Alias for {@link id}.
  101. *
  102. * @returns {number}
  103. *
  104. * @deprecated
  105. */
  106. get messageID () {
  107. warning.emit('LDAP_MESSAGE_DEP_001')
  108. return this.id
  109. }
  110. /**
  111. * Alias for {@link id}.
  112. *
  113. * @param {number} value
  114. *
  115. * @deprecated
  116. */
  117. set messageID (value) {
  118. warning.emit('LDAP_MESSAGE_DEP_001')
  119. this.id = value
  120. }
  121. /**
  122. * Message type specific. Each message type must implement a `_dn` property
  123. * that provides this value.
  124. *
  125. * @type {import('@ldapjs/dn').DN}
  126. */
  127. get dn () {
  128. return this._dn
  129. }
  130. /**
  131. * The LDAP protocol operation code for the message.
  132. *
  133. * @type {number}
  134. */
  135. get protocolOp () {
  136. return this.#protocolOp
  137. }
  138. /**
  139. * The name of the message class.
  140. *
  141. * @type {string}
  142. */
  143. get type () {
  144. return 'LdapMessage'
  145. }
  146. /**
  147. * Use {@link pojo} instead.
  148. *
  149. * @deprecated
  150. */
  151. get json () {
  152. warning.emit('LDAP_MESSAGE_DEP_002')
  153. return this.pojo
  154. }
  155. /**
  156. * A serialized representation of the message as a plain JavaScript object.
  157. * Specific message types must implement the `_pojo(obj)` method. The passed
  158. * in `obj` must be extended with the specific message's unique properties
  159. * and returned as the result.
  160. *
  161. * @returns {object}
  162. */
  163. get pojo () {
  164. let result = {
  165. messageId: this.id,
  166. protocolOp: this.#protocolOp,
  167. type: this.type
  168. }
  169. if (typeof this._pojo === 'function') {
  170. result = this._pojo(result)
  171. }
  172. result.controls = this.#controls.map(c => c.pojo)
  173. return result
  174. }
  175. addControl (control) {
  176. this.#controls.push(control)
  177. }
  178. /**
  179. * Converts an {@link LdapMessage} object into a set of BER bytes that can
  180. * be sent across the wire. Specific message implementations must implement
  181. * the `_toBer(ber)` method. This method will write its unique sequence(s)
  182. * to the passed in `ber` object.
  183. *
  184. * @returns {import('@ldapjs/asn1').BerReader}
  185. */
  186. toBer () {
  187. if (typeof this._toBer !== 'function') {
  188. throw Error(`${this.type} does not implement _toBer`)
  189. }
  190. const writer = new BerWriter()
  191. writer.startSequence()
  192. writer.writeInt(this.id)
  193. this._toBer(writer)
  194. if (this.#controls.length > 0) {
  195. writer.startSequence(0xa0)
  196. for (const control of this.#controls) {
  197. control.toBer(writer)
  198. }
  199. writer.endSequence()
  200. }
  201. writer.endSequence()
  202. return new BerReader(writer.buffer)
  203. }
  204. /**
  205. * Serializes the message into a JSON representation.
  206. *
  207. * @returns {string}
  208. */
  209. toString () {
  210. return JSON.stringify(this.pojo)
  211. }
  212. /**
  213. * Parses a BER into a message object. The offset of the BER _must_ point
  214. * to the start of an LDAP Message sequence. That is, the first few bytes
  215. * must indicate:
  216. *
  217. * 1. a sequence tag and how many bytes are in that sequence
  218. * 2. an integer representing the message identifier
  219. * 3. a protocol operation, e.g. BindRequest, and the number of bytes in
  220. * that operation
  221. *
  222. * @param {import('@ldapjs/asn1').BerReader} ber
  223. *
  224. * @returns {LdapMessage}
  225. */
  226. static parse (ber) {
  227. // We must require here because `parseToMessage` imports subclasses
  228. // that need `LdapMessage` to be defined. If we try importing earlier,
  229. // then `LdapMessage` will not be available, and we will get errors about
  230. // trying to subclass null objects.
  231. return require('./parse-to-message')(ber)
  232. }
  233. /**
  234. * When invoked on specific message types, e.g. {@link BindRequest}, this
  235. * method will parse a BER into a plain JavaScript object that is usable as
  236. * an options object for constructing that specific message object.
  237. *
  238. * @param {import('@ldapjs/asn1').BerReader} ber A BER to parse. The reader
  239. * offset must point to the start of a valid sequence, i.e. the "tag" byte
  240. * in the TLV tuple, that represents the message to be parsed. For example,
  241. * in a {@link BindRequest} the starting sequence and message identifier must
  242. * already be read such that the offset is at the protocol operation sequence
  243. * byte.
  244. */
  245. static parseToPojo (ber) {
  246. throw Error('Use LdapMessage.parse, or a specific message type\'s parseToPojo, instead.')
  247. }
  248. }
  249. module.exports = LdapMessage