add-request.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. 'use strict'
  2. const LdapMessage = require('../ldap-message')
  3. const Attribute = require('@ldapjs/attribute')
  4. const Protocol = require('@ldapjs/protocol')
  5. const { DN } = require('@ldapjs/dn')
  6. /**
  7. * Implements the add request message as described in
  8. * https://www.rfc-editor.org/rfc/rfc4511.html#section-4.7
  9. */
  10. class AddRequest extends LdapMessage {
  11. /**
  12. * Path to the LDAP object.
  13. *
  14. * @type {null | import('@ldapjs/dn').DN}
  15. */
  16. #entry
  17. /**
  18. * A set of attribute objects.
  19. *
  20. * @type {import('@ldapjs/attribute')[]}
  21. */
  22. #attributes = []
  23. /**
  24. * @typedef {LdapMessageOptions} AddRequestOptions
  25. * @property {string} [entry=null] The path to the LDAP object.
  26. * @property {import('@ldapjs/attribute')[]} [attributes=[]] A set of
  27. * attributes to store at the `entry` path.
  28. */
  29. /**
  30. * @param {AddRequestOptions} [options]
  31. *
  32. * @throws When the provided attributes list is invalid.
  33. */
  34. constructor (options = {}) {
  35. options.protocolOp = Protocol.operations.LDAP_REQ_ADD
  36. super(options)
  37. this.entry = options.entry || null
  38. this.attributes = options.attributes || []
  39. }
  40. /**
  41. * Get a copy of the attributes associated with the request.
  42. *
  43. * @returns {import('@ldapjs/attribute')[]}
  44. */
  45. get attributes () {
  46. return this.#attributes.slice(0)
  47. }
  48. /**
  49. * Set the attributes to be added to the entry. Replaces any existing
  50. * attributes.
  51. *
  52. * @param {import('@ldapjs/attribute')[]} attrs
  53. *
  54. * @throws If the input is not an array, or any element is not an
  55. * {@link Attribute} or attribute-like object.
  56. */
  57. set attributes (attrs) {
  58. if (Array.isArray(attrs) === false) {
  59. throw Error('attrs must be an array')
  60. }
  61. const newAttrs = []
  62. for (const attr of attrs) {
  63. if (Attribute.isAttribute(attr) === false) {
  64. throw Error('attr must be an Attribute instance or Attribute-like object')
  65. }
  66. if (Object.prototype.toString.call(attr) !== '[object LdapAttribute]') {
  67. newAttrs.push(new Attribute(attr))
  68. continue
  69. }
  70. newAttrs.push(attr)
  71. }
  72. this.#attributes = newAttrs
  73. }
  74. /**
  75. * The directory path to the object to add.
  76. *
  77. * @type {string}
  78. */
  79. get entry () {
  80. return this.#entry ?? null
  81. }
  82. /**
  83. * Define the entry path to the LDAP object.
  84. *
  85. * @param {string | import('@ldapjs/dn').DN} path
  86. */
  87. set entry (path) {
  88. if (path === null) return
  89. if (typeof path === 'string') {
  90. this.#entry = DN.fromString(path)
  91. } else if (Object.prototype.toString.call(path) === '[object LdapDn]') {
  92. this.#entry = path
  93. } else {
  94. throw Error('entry must be a valid DN string or instance of LdapDn')
  95. }
  96. }
  97. /**
  98. * Alias of {@link entry}.
  99. *
  100. * @type {string}
  101. */
  102. get _dn () {
  103. return this.entry
  104. }
  105. /**
  106. * The name of the request type.
  107. *
  108. * @type {string}
  109. */
  110. get type () {
  111. return 'AddRequest'
  112. }
  113. /**
  114. * Add a new {@link Attribute} to the list of request attributes.
  115. *
  116. * @param {import('@ldapjs/attribute')} attr
  117. *
  118. * @throws When the input is not an {@link Attribute} instance.
  119. */
  120. addAttribute (attr) {
  121. if (Object.prototype.toString.call(attr) !== '[object LdapAttribute]') {
  122. throw Error('attr must be an instance of Attribute')
  123. }
  124. this.#attributes.push(attr)
  125. }
  126. /**
  127. * Get the list of attribute names for the attributes in the
  128. * request.
  129. *
  130. * @returns {string[]}
  131. */
  132. attributeNames () {
  133. return this.#attributes.map(attr => attr.type)
  134. }
  135. /**
  136. * Retrieve an attribute by name from the attributes associated with
  137. * the request.
  138. *
  139. * @param {string} attributeName
  140. *
  141. * @returns {import('@ldapjs/attribute')|null}
  142. *
  143. * @throws When `attributeName` is not a string.
  144. */
  145. getAttribute (attributeName) {
  146. if (typeof attributeName !== 'string') {
  147. throw Error('attributeName must be a string')
  148. }
  149. for (const attr of this.#attributes) {
  150. if (attr.type === attributeName) {
  151. return attr
  152. }
  153. }
  154. return null
  155. }
  156. /**
  157. * Find the index of an {@link Attribute} in the request's
  158. * attribute set.
  159. *
  160. * @param {string} attributeName
  161. *
  162. * @returns {number} The index of the attribute, or `-1` if not
  163. * found.
  164. *
  165. * @throws When `attributeName` is not a string.
  166. */
  167. indexOf (attributeName) {
  168. if (typeof attributeName !== 'string') {
  169. throw Error('attributeName must be a string')
  170. }
  171. for (let i = 0; i < this.#attributes.length; i += 1) {
  172. if (this.#attributes[i].type === attributeName) {
  173. return i
  174. }
  175. }
  176. return -1
  177. }
  178. /**
  179. * Internal use only.
  180. *
  181. * @param {import('@ldapjs/asn1').BerWriter} ber
  182. *
  183. * @returns {import('@ldapjs/asn1').BerWriter}
  184. */
  185. _toBer (ber) {
  186. ber.startSequence(Protocol.operations.LDAP_REQ_ADD)
  187. ber.writeString(this.#entry.toString())
  188. ber.startSequence()
  189. for (const attr of this.#attributes) {
  190. const attrBer = attr.toBer()
  191. ber.appendBuffer(attrBer.buffer)
  192. }
  193. ber.endSequence()
  194. ber.endSequence()
  195. return ber
  196. }
  197. /**
  198. * Internal use only.
  199. *
  200. * @param {object}
  201. *
  202. * @returns {object}
  203. */
  204. _pojo (obj = {}) {
  205. obj.entry = this.#entry ? this.#entry.toString() : null
  206. obj.attributes = []
  207. for (const attr of this.#attributes) {
  208. obj.attributes.push(attr.pojo)
  209. }
  210. return obj
  211. }
  212. /**
  213. * Implements the standardized `parseToPojo` method.
  214. *
  215. * @see LdapMessage.parseToPojo
  216. *
  217. * @param {import('@ldapjs/asn1').BerReader} ber
  218. *
  219. * @returns {object}
  220. */
  221. static parseToPojo (ber) {
  222. const protocolOp = ber.readSequence()
  223. if (protocolOp !== Protocol.operations.LDAP_REQ_ADD) {
  224. const op = protocolOp.toString(16).padStart(2, '0')
  225. throw Error(`found wrong protocol operation: 0x${op}`)
  226. }
  227. const entry = ber.readString()
  228. const attributes = []
  229. // Advance to the first attribute sequence in the set
  230. // of attribute sequences.
  231. ber.readSequence()
  232. const endOfAttributesPos = ber.offset + ber.length
  233. while (ber.offset < endOfAttributesPos) {
  234. const attribute = Attribute.fromBer(ber)
  235. attribute.type = attribute.type.toLowerCase()
  236. if (attribute.type === 'objectclass') {
  237. for (let i = 0; i < attribute.values.length; i++) {
  238. attribute.values[i] = attribute.values[i].toLowerCase()
  239. }
  240. }
  241. attributes.push(attribute)
  242. }
  243. return { protocolOp, entry, attributes }
  244. }
  245. }
  246. module.exports = AddRequest