ldap-result.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. 'use strict'
  2. const LdapMessage = require('./ldap-message')
  3. const { resultCodes, operations } = require('@ldapjs/protocol')
  4. const warning = require('./deprecations')
  5. /**
  6. * Implements the base LDAP response message as defined in
  7. * https://www.rfc-editor.org/rfc/rfc4511.html#section-4.1.9.
  8. */
  9. class LdapResult extends LdapMessage {
  10. #connection = null
  11. #diagnosticMessage
  12. #matchedDN
  13. #referrals = []
  14. #status
  15. /**
  16. * @typedef {LdapMessageOptions} LdapResultOptions
  17. * @property {number} [status=0] An LDAP status code.
  18. * @param {string} [matchedDN=''] The DN that matched the request.
  19. * @param {string[]} [referrals=[]] A set of servers to query for references.
  20. * @param {string} [diagnosticMessage] A message indicating why a request
  21. * failed.
  22. */
  23. /**
  24. * @param {LdapResultOptions} options
  25. */
  26. constructor (options = {}) {
  27. super(options)
  28. this.#status = options.status ?? resultCodes.SUCCESS
  29. this.#matchedDN = options.matchedDN || ''
  30. this.#referrals = options.referrals || []
  31. this.#diagnosticMessage = options.diagnosticMessage || options.errorMessage || ''
  32. if (options.errorMessage) {
  33. warning.emit('LDAP_MESSAGE_DEP_004')
  34. }
  35. }
  36. /**
  37. * The failure message as returned by the server if one is present.
  38. *
  39. * @returns {string}
  40. */
  41. get diagnosticMessage () {
  42. return this.#diagnosticMessage
  43. }
  44. /**
  45. * Add a diagnostic message to the instance.
  46. *
  47. * @param {string} message
  48. */
  49. set diagnosticMessage (message) {
  50. this.#diagnosticMessage = message
  51. }
  52. /**
  53. * The DN that a request matched.
  54. *
  55. * @returns {string}
  56. */
  57. get matchedDN () {
  58. return this.#matchedDN
  59. }
  60. /**
  61. * Define which DN a request matched.
  62. *
  63. * @param {string} dn
  64. */
  65. set matchedDN (dn) {
  66. this.#matchedDN = dn
  67. }
  68. /**
  69. * A serialized representation of the message as a plain JavaScript object.
  70. * Specific message types must implement the `_pojo(obj)` method. The passed
  71. * in `obj` must be extended with the specific message's unique properties
  72. * and returned as the result.
  73. *
  74. * @returns {object}
  75. */
  76. get pojo () {
  77. let result = {
  78. status: this.status,
  79. matchedDN: this.matchedDN,
  80. diagnosticMessage: this.diagnosticMessage,
  81. referrals: this.referrals
  82. }
  83. if (typeof this._pojo === 'function') {
  84. result = this._pojo(result)
  85. }
  86. return result
  87. }
  88. /**
  89. * The list of servers that should be consulted to get an answer
  90. * to the query.
  91. *
  92. * @returns {string[]}
  93. */
  94. get referrals () {
  95. return this.#referrals.slice(0)
  96. }
  97. /**
  98. * The LDAP response code for the request.
  99. *
  100. * @returns {number}
  101. */
  102. get status () {
  103. return this.#status
  104. }
  105. /**
  106. * Set the response code for the request.
  107. *
  108. * @param {number} s
  109. */
  110. set status (s) {
  111. this.#status = s
  112. }
  113. /**
  114. * The name of the request type.
  115. *
  116. * @type {string}
  117. */
  118. get type () {
  119. return 'LdapResult'
  120. }
  121. /**
  122. * Add a new server to the list of servers that should be
  123. * consulted for an answer to the query.
  124. *
  125. * @param {string} referral
  126. */
  127. addReferral (referral) {
  128. this.#referrals.push(referral)
  129. }
  130. /**
  131. * Internal use only. Subclasses may implement a `_writeResponse`
  132. * method to add to the sequence after any referrals.
  133. *
  134. * @param {import('@ldapjs/asn1').BerWriter} ber
  135. *
  136. * @returns {import('@ldapjs/asn1').BerWriter}
  137. *
  138. * @private
  139. */
  140. _toBer (ber) {
  141. ber.startSequence(this.protocolOp)
  142. ber.writeEnumeration(this.status)
  143. ber.writeString(this.matchedDN)
  144. ber.writeString(this.diagnosticMessage)
  145. if (this.referrals.length > 0) {
  146. ber.startSequence(operations.LDAP_RES_REFERRAL)
  147. ber.writeStringArray(this.referrals)
  148. ber.endSequence()
  149. }
  150. if (typeof this._writeResponse === 'function') {
  151. this._writeResponse(ber)
  152. }
  153. ber.endSequence()
  154. }
  155. /**
  156. * When invoked on specific message types, e.g. {@link AddResponse}, this
  157. * method will parse a BER into a plain JavaScript object that is usable as
  158. * an options object for constructing that specific message object.
  159. *
  160. * @param {import('@ldapjs/asn1').BerReader} ber A BER to parse. The reader
  161. * offset must point to the start of a valid sequence, i.e. the "tag" byte
  162. * in the TLV tuple, that represents the message to be parsed. For example,
  163. * in a {@link AddResponse} the starting sequence and message identifier must
  164. * already be read such that the offset is at the protocol operation sequence
  165. * byte.
  166. */
  167. static parseToPojo (ber) {
  168. throw Error('Use LdapMessage.parse, or a specific message type\'s parseToPojo, instead.')
  169. }
  170. /**
  171. * Internal use only.
  172. *
  173. * Response messages are a little more generic to parse than request messages.
  174. * However, they still need to recognize the correct protocol operation. So
  175. * the public {@link parseToPojo} for each response object should invoke this
  176. * private static method to parse the BER and indicate the correct protocol
  177. * operation to recognize.
  178. *
  179. * @param {object} input
  180. * @param {number} input.opCode The expected protocol operation to look for.
  181. * @param {import('@ldapjs/asn1').BerReader} berReader The BER to process. It
  182. * must start at an offset representing a protocol operation tag.
  183. * @param {object} [input.pojo] A plain JavaScript object to populate with
  184. * the parsed keys and values.
  185. *
  186. * @returns {object}
  187. *
  188. * @private
  189. */
  190. static _parseToPojo ({ opCode, berReader, pojo = {} }) {
  191. const protocolOp = berReader.readSequence()
  192. if (protocolOp !== opCode) {
  193. const op = protocolOp.toString(16).padStart(2, '0')
  194. throw Error(`found wrong protocol operation: 0x${op}`)
  195. }
  196. const status = berReader.readEnumeration()
  197. const matchedDN = berReader.readString()
  198. const diagnosticMessage = berReader.readString()
  199. const referrals = []
  200. if (berReader.peek() === operations.LDAP_RES_REFERRAL) {
  201. // Advance the offset to the start of the value and
  202. // put the sequence length into the `reader.length` field.
  203. berReader.readSequence(operations.LDAP_RES_REFERRAL)
  204. const end = berReader.length
  205. while (berReader.offset < end) {
  206. referrals.push(berReader.readString())
  207. }
  208. }
  209. pojo.status = status
  210. pojo.matchedDN = matchedDN
  211. pojo.diagnosticMessage = diagnosticMessage
  212. pojo.referrals = referrals
  213. return pojo
  214. }
  215. }
  216. module.exports = LdapResult