search-result-entry.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. 'use strict'
  2. const LdapMessage = require('../ldap-message')
  3. const Attribute = require('@ldapjs/attribute')
  4. const { operations } = require('@ldapjs/protocol')
  5. const { DN } = require('@ldapjs/dn')
  6. /**
  7. * Implements the search result entry message as described in
  8. * https://www.rfc-editor.org/rfc/rfc4511.html#section-4.5.2.
  9. */
  10. class SearchResultEntry extends LdapMessage {
  11. /**
  12. * Path to the LDAP object.
  13. *
  14. * @type {import('@ldapjs/dn').DN}
  15. */
  16. #objectName
  17. /**
  18. * A set of attribute objects.
  19. *
  20. * @type {import('@ldapjs/attribute')[]}
  21. */
  22. #attributes = []
  23. /**
  24. * @typedef {LdapMessageOptions} SearchResultEntryOptions
  25. * @property {string | import('@ldapjs/dn').DN} [objectName=''] The path to
  26. * the LDAP object.
  27. * @property {import('@ldapjs/attribute')[]} attributes A set of attributes
  28. * to store at the `entry` path.
  29. */
  30. /**
  31. * @param {SearchResultEntryOptions} [options]
  32. *
  33. * @throws When the provided attributes list is invalid or the object name
  34. * is not a valid LdapDn object or DN string.
  35. */
  36. constructor (options = {}) {
  37. options.protocolOp = operations.LDAP_RES_SEARCH_ENTRY
  38. super(options)
  39. this.objectName = options.objectName ?? ''
  40. this.attributes = options.attributes ?? []
  41. }
  42. /**
  43. * Alias of {@link objectName}.
  44. *
  45. * @type {string}
  46. */
  47. get _dn () {
  48. return this.#objectName
  49. }
  50. /**
  51. * The name of the request type.
  52. *
  53. * @type {string}
  54. */
  55. get type () {
  56. return 'SearchResultEntry'
  57. }
  58. /**
  59. * Get a copy of the attributes associated with the request.
  60. *
  61. * @returns {import('@ldapjs/attribute')[]}
  62. */
  63. get attributes () {
  64. return this.#attributes.slice(0)
  65. }
  66. /**
  67. * Set the attributes to be added to the entry. Replaces any existing
  68. * attributes.
  69. *
  70. * @param {object[] | import('@ldapjs/attribute')[]} attrs
  71. *
  72. * @throws If the input is not an array, or any element is not an
  73. * {@link Attribute} or attribute-like object.
  74. */
  75. set attributes (attrs) {
  76. if (Array.isArray(attrs) === false) {
  77. throw Error('attrs must be an array')
  78. }
  79. const newAttrs = []
  80. for (const attr of attrs) {
  81. if (Attribute.isAttribute(attr) === false) {
  82. throw Error('attr must be an Attribute instance or Attribute-like object')
  83. }
  84. if (Object.prototype.toString.call(attr) !== '[object LdapAttribute]') {
  85. newAttrs.push(new Attribute(attr))
  86. continue
  87. }
  88. newAttrs.push(attr)
  89. }
  90. this.#attributes = newAttrs
  91. }
  92. /**
  93. * The path to the LDAP entry that matched the search.
  94. *
  95. * @returns {import('@ldapjs/dn').DN}
  96. */
  97. get objectName () {
  98. return this.#objectName
  99. }
  100. /**
  101. * Set the path to the LDAP entry that matched the search.
  102. *
  103. * @param {string | import('@ldapjs/dn').DN} value
  104. *
  105. * @throws When the input is invalid.
  106. */
  107. set objectName (value) {
  108. if (typeof value === 'string') {
  109. this.#objectName = DN.fromString(value)
  110. } else if (Object.prototype.toString.call(value) === '[object LdapDn]') {
  111. this.#objectName = value
  112. } else {
  113. throw Error('objectName must be a DN string or an instance of LdapDn')
  114. }
  115. }
  116. /**
  117. * Internal use only.
  118. *
  119. * @param {import('@ldapjs/asn1').BerWriter} ber
  120. *
  121. * @returns {import('@ldapjs/asn1').BerWriter}
  122. */
  123. _toBer (ber) {
  124. ber.startSequence(operations.LDAP_RES_SEARCH_ENTRY)
  125. ber.writeString(this.#objectName.toString())
  126. ber.startSequence()
  127. for (const attr of this.#attributes) {
  128. const attrBer = attr.toBer()
  129. ber.appendBuffer(attrBer.buffer)
  130. }
  131. ber.endSequence()
  132. ber.endSequence()
  133. return ber
  134. }
  135. /**
  136. * Internal use only.
  137. *
  138. * @param {object}
  139. *
  140. * @returns {object}
  141. */
  142. _pojo (obj = {}) {
  143. obj.objectName = this.#objectName.toString()
  144. obj.attributes = []
  145. for (const attr of this.#attributes) {
  146. obj.attributes.push(attr.pojo)
  147. }
  148. return obj
  149. }
  150. /**
  151. * Implements the standardized `parseToPojo` method.
  152. *
  153. * @see LdapMessage.parseToPojo
  154. *
  155. * @param {import('@ldapjs/asn1').BerReader} ber
  156. *
  157. * @returns {object}
  158. */
  159. static parseToPojo (ber) {
  160. const protocolOp = ber.readSequence()
  161. if (protocolOp !== operations.LDAP_RES_SEARCH_ENTRY) {
  162. const op = protocolOp.toString(16).padStart(2, '0')
  163. throw Error(`found wrong protocol operation: 0x${op}`)
  164. }
  165. const objectName = ber.readString()
  166. const attributes = []
  167. // Advance to the first attribute sequence in the set
  168. // of attribute sequences.
  169. ber.readSequence()
  170. const endOfAttributesPos = ber.offset + ber.length
  171. while (ber.offset < endOfAttributesPos) {
  172. const attribute = Attribute.fromBer(ber)
  173. attributes.push(attribute)
  174. }
  175. return { protocolOp, objectName, attributes }
  176. }
  177. }
  178. module.exports = SearchResultEntry