Replace messages with @ldapjs/messages

This commit is contained in:
James Sumners 2023-02-21 13:28:58 -05:00
parent 685465843d
commit f18dee40a2
68 changed files with 537 additions and 5173 deletions

View File

@ -4,6 +4,9 @@ module.exports = {
es2021: true,
node: true
},
parserOptions: {
ecmaVersion: 'latest'
},
extends: [
'standard'
],

4
.npmrc
View File

@ -1,7 +1,3 @@
# npm general settings
package-lock=false
legacy-peer-deps=true
# pnpm specific settings
hoist=false
public-hoist-pattern[]=*eslint*

View File

@ -41,7 +41,6 @@ client is:
|connectTimeout |Milliseconds client should wait before timing out on TCP connections (Default: OS default)|
|tlsOptions |Additional options passed to TLS connection layer when connecting via `ldaps://` (See: The TLS docs for node.js)|
|idleTimeout |Milliseconds after last activity before client emits idle event|
|strictDN |Force strict DN parsing for client methods (Default is true)|
|reconnect |Try to reconnect when the connection gets lost (Default is false)|
### url
@ -287,7 +286,7 @@ Responses inside callback of the `search` method are an `EventEmitter` where you
each `searchEntry` that comes back from the server. You will additionally be able to listen for a `searchRequest`
, `searchReference`, `error` and `end` event.
`searchRequest` is emitted immediately after every `SearchRequest` is sent with a `SearchRequest` parameter. You can do operations
like `client.abandon` with `searchRequest.messageID` to abandon this search request. Note that the `error` event will
like `client.abandon` with `searchRequest.messageId` to abandon this search request. Note that the `error` event will
only be for client/TCP errors, not LDAP error codes like the other APIs. You'll want to check the LDAP status code
(likely for `0`) on the `end` event to assert success. LDAP search results can give you a lot of status codes, such as
time or size exceeded, busy, inappropriate matching, etc., which is why this method doesn't try to wrap up the code
@ -306,7 +305,7 @@ client.search('o=example', opts, (err, res) => {
assert.ifError(err);
res.on('searchRequest', (searchRequest) => {
console.log('searchRequest: ', searchRequest.messageID);
console.log('searchRequest: ', searchRequest.messageId);
});
res.on('searchEntry', (entry) => {
console.log('entry: ' + JSON.stringify(entry.object));

View File

@ -197,7 +197,7 @@ authenticated as on this connection. If the client didn't bind, then a DN object
will be there defaulted to `cn=anonymous`.
Additionally, request will have a `logId` parameter you can use to uniquely
identify the request/connection pair in logs (includes the LDAP messageID).
identify the request/connection pair in logs (includes the LDAP messageId).
## Common Response Elements

View File

@ -1,54 +0,0 @@
// Copyright 2015 Joyent, Inc.
const assert = require('assert')
const util = require('util')
const isDN = require('./dn').DN.isDN
const isAttribute = require('./attribute').isAttribute
/// --- Helpers
// Copied from mcavage/node-assert-plus
function _assert (arg, type, name) {
name = name || type
throw new assert.AssertionError({
message: util.format('%s (%s) required', name, type),
actual: typeof (arg),
expected: type,
operator: '===',
stackStartFunction: _assert.caller
})
}
/// --- API
function stringDN (input, name) {
if (isDN(input) || typeof (input) === 'string') { return }
_assert(input, 'DN or string', name)
}
function optionalStringDN (input, name) {
if (input === undefined || isDN(input) || typeof (input) === 'string') { return }
_assert(input, 'DN or string', name)
}
function optionalDN (input, name) {
if (input !== undefined && !isDN(input)) { _assert(input, 'DN', name) }
}
function optionalArrayOfAttribute (input, name) {
if (input === undefined) { return }
if (!Array.isArray(input) ||
input.some(function (v) { return !isAttribute(v) })) {
_assert(input, 'array of Attribute', name)
}
}
/// --- Exports
module.exports = {
stringDN: stringDN,
optionalStringDN: optionalStringDN,
optionalDN: optionalDN,
optionalArrayOfAttribute: optionalArrayOfAttribute
}

View File

@ -1,160 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert')
const asn1 = require('@ldapjs/asn1')
const Protocol = require('@ldapjs/protocol')
/// --- API
function Attribute (options) {
if (options) {
if (typeof (options) !== 'object') { throw new TypeError('options must be an object') }
if (options.type && typeof (options.type) !== 'string') { throw new TypeError('options.type must be a string') }
} else {
options = {}
}
this.type = options.type || ''
this._vals = []
if (options.vals !== undefined && options.vals !== null) { this.vals = options.vals }
}
module.exports = Attribute
Object.defineProperties(Attribute.prototype, {
buffers: {
get: function getBuffers () {
return this._vals
},
configurable: false
},
json: {
get: function getJson () {
return {
type: this.type,
vals: this.vals
}
},
configurable: false
},
vals: {
get: function getVals () {
const eType = _bufferEncoding(this.type)
return this._vals.map(function (v) {
return v.toString(eType)
})
},
set: function setVals (vals) {
const self = this
this._vals = []
if (Array.isArray(vals)) {
vals.forEach(function (v) {
self.addValue(v)
})
} else {
self.addValue(vals)
}
},
configurable: false
}
})
Attribute.prototype.addValue = function addValue (val) {
if (Buffer.isBuffer(val)) {
this._vals.push(val)
} else {
this._vals.push(Buffer.from(val + '', _bufferEncoding(this.type)))
}
}
/* BEGIN JSSTYLED */
Attribute.compare = function compare (a, b) {
if (!(Attribute.isAttribute(a)) || !(Attribute.isAttribute(b))) {
throw new TypeError('can only compare Attributes')
}
if (a.type < b.type) return -1
if (a.type > b.type) return 1
if (a.vals.length < b.vals.length) return -1
if (a.vals.length > b.vals.length) return 1
for (let i = 0; i < a.vals.length; i++) {
if (a.vals[i] < b.vals[i]) return -1
if (a.vals[i] > b.vals[i]) return 1
}
return 0
}
/* END JSSTYLED */
Attribute.prototype.parse = function parse (ber) {
assert.ok(ber)
ber.readSequence()
this.type = ber.readString()
if (ber.peek() === Protocol.core.LBER_SET) {
if (ber.readSequence(Protocol.core.LBER_SET)) {
const end = ber.offset + ber.length
while (ber.offset < end) { this._vals.push(ber.readString(asn1.Ber.OctetString, true)) }
}
}
return true
}
Attribute.prototype.toBer = function toBer (ber) {
assert.ok(ber)
ber.startSequence()
ber.writeString(this.type)
ber.startSequence(Protocol.core.LBER_SET)
if (this._vals.length) {
this._vals.forEach(function (b) {
ber.writeByte(asn1.Ber.OctetString)
ber.writeLength(b.length)
for (let i = 0; i < b.length; i++) { ber.writeByte(b[i]) }
})
} else {
ber.writeStringArray([])
}
ber.endSequence()
ber.endSequence()
return ber
}
Attribute.prototype.toString = function () {
return JSON.stringify(this.json)
}
Attribute.toBer = function (attr, ber) {
return Attribute.prototype.toBer.call(attr, ber)
}
Attribute.isAttribute = function isAttribute (attr) {
if (!attr || typeof (attr) !== 'object') {
return false
}
if (attr instanceof Attribute) {
return true
}
if ((typeof (attr.toBer) === 'function') &&
(typeof (attr.type) === 'string') &&
(Array.isArray(attr.vals)) &&
(attr.vals.filter(function (item) {
return (typeof (item) === 'string' ||
Buffer.isBuffer(item))
}).length === attr.vals.length)) {
return true
}
return false
}
function _bufferEncoding (type) {
/* JSSTYLED */
return /;binary$/.test(type) ? 'base64' : 'utf8'
}

View File

@ -1,212 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const Attribute = require('./attribute')
/// --- API
function Change (options) {
if (options) {
assert.object(options)
assert.optionalString(options.operation)
} else {
options = {}
}
this._modification = false
this.operation = options.operation || options.type || 'add'
this.modification = options.modification || {}
}
Object.defineProperties(Change.prototype, {
operation: {
get: function getOperation () {
switch (this._operation) {
case 0x00: return 'add'
case 0x01: return 'delete'
case 0x02: return 'replace'
default:
throw new Error('0x' + this._operation.toString(16) + ' is invalid')
}
},
set: function setOperation (val) {
assert.string(val)
switch (val.toLowerCase()) {
case 'add':
this._operation = 0x00
break
case 'delete':
this._operation = 0x01
break
case 'replace':
this._operation = 0x02
break
default:
throw new Error('Invalid operation type: 0x' + val.toString(16))
}
},
configurable: false
},
modification: {
get: function getModification () {
return this._modification
},
set: function setModification (val) {
if (Attribute.isAttribute(val)) {
this._modification = val
return
}
// Does it have an attribute-like structure
if (Object.keys(val).length === 2 &&
typeof (val.type) === 'string' &&
Array.isArray(val.vals)) {
this._modification = new Attribute({
type: val.type,
vals: val.vals
})
return
}
const keys = Object.keys(val)
if (keys.length > 1) {
throw new Error('Only one attribute per Change allowed')
} else if (keys.length === 0) {
return
}
const k = keys[0]
const _attr = new Attribute({ type: k })
if (Array.isArray(val[k])) {
val[k].forEach(function (v) {
_attr.addValue(v.toString())
})
} else if (Buffer.isBuffer(val[k])) {
_attr.addValue(val[k])
} else if (val[k] !== undefined && val[k] !== null) {
_attr.addValue(val[k].toString())
}
this._modification = _attr
},
configurable: false
},
json: {
get: function getJSON () {
return {
operation: this.operation,
modification: this._modification ? this._modification.json : {}
}
},
configurable: false
}
})
Change.isChange = function isChange (change) {
if (!change || typeof (change) !== 'object') {
return false
}
if ((change instanceof Change) ||
((typeof (change.toBer) === 'function') &&
(change.modification !== undefined) &&
(change.operation !== undefined))) {
return true
}
return false
}
Change.compare = function (a, b) {
if (!Change.isChange(a) || !Change.isChange(b)) { throw new TypeError('can only compare Changes') }
if (a.operation < b.operation) { return -1 }
if (a.operation > b.operation) { return 1 }
return Attribute.compare(a.modification, b.modification)
}
/**
* Apply a Change to properties of an object.
*
* @param {Object} change the change to apply.
* @param {Object} obj the object to apply it to.
* @param {Boolean} scalar convert single-item arrays to scalars. Default: false
*/
Change.apply = function apply (change, obj, scalar) {
assert.string(change.operation)
assert.string(change.modification.type)
assert.ok(Array.isArray(change.modification.vals))
assert.object(obj)
const type = change.modification.type
const vals = change.modification.vals
let data = obj[type]
if (data !== undefined) {
if (!Array.isArray(data)) {
data = [data]
}
} else {
data = []
}
switch (change.operation) {
case 'replace':
if (vals.length === 0) {
// replace empty is a delete
delete obj[type]
return obj
} else {
data = vals
}
break
case 'add': {
// add only new unique entries
const newValues = vals.filter(function (entry) {
return (data.indexOf(entry) === -1)
})
data = data.concat(newValues)
break
}
case 'delete':
data = data.filter(function (entry) {
return (vals.indexOf(entry) === -1)
})
if (data.length === 0) {
// Erase the attribute if empty
delete obj[type]
return obj
}
break
default:
break
}
if (scalar && data.length === 1) {
// store single-value outputs as scalars, if requested
obj[type] = data[0]
} else {
obj[type] = data
}
return obj
}
Change.prototype.parse = function (ber) {
assert.ok(ber)
ber.readSequence()
this._operation = ber.readEnumeration()
this._modification = new Attribute()
this._modification.parse(ber)
return true
}
Change.prototype.toBer = function (ber) {
assert.ok(ber)
ber.startSequence()
ber.writeEnumeration(this._operation)
ber = this._modification.toBer(ber)
ber.endSequence()
return ber
}
/// --- Exports
module.exports = Change

View File

@ -15,38 +15,37 @@ const vasync = require('vasync')
const assert = require('assert-plus')
const VError = require('verror').VError
const Attribute = require('../attribute')
const Change = require('../change')
const Attribute = require('@ldapjs/attribute')
const Change = require('@ldapjs/change')
const Control = require('../controls/index').Control
const { Control: LdapControl } = require('@ldapjs/controls')
const SearchPager = require('./search_pager')
const Protocol = require('@ldapjs/protocol')
const dn = require('../dn')
const { DN } = require('@ldapjs/dn')
const errors = require('../errors')
const filters = require('@ldapjs/filter')
const messages = require('../messages')
const Parser = require('../messages/parser')
const url = require('../url')
const CorkedEmitter = require('../corked_emitter')
/// --- Globals
const AbandonRequest = messages.AbandonRequest
const AddRequest = messages.AddRequest
const BindRequest = messages.BindRequest
const CompareRequest = messages.CompareRequest
const DeleteRequest = messages.DeleteRequest
const ExtendedRequest = messages.ExtendedRequest
const ModifyRequest = messages.ModifyRequest
const ModifyDNRequest = messages.ModifyDNRequest
const SearchRequest = messages.SearchRequest
const UnbindRequest = messages.UnbindRequest
const UnbindResponse = messages.UnbindResponse
const LDAPResult = messages.LDAPResult
const SearchEntry = messages.SearchEntry
const SearchReference = messages.SearchReference
// var SearchResponse = messages.SearchResponse
const Parser = messages.Parser
const messages = require('@ldapjs/messages')
const {
AbandonRequest,
AddRequest,
BindRequest,
CompareRequest,
DeleteRequest,
ExtensionRequest: ExtendedRequest,
ModifyRequest,
ModifyDnRequest: ModifyDNRequest,
SearchRequest,
UnbindRequest,
LdapResult: LDAPResult,
SearchResultEntry: SearchEntry,
SearchResultReference: SearchReference
} = messages
const PresenceFilter = filters.PresenceFilter
@ -79,13 +78,11 @@ function validateControls (controls) {
return controls
}
function ensureDN (input, strict) {
if (dn.DN.isDN(input)) {
return dn
} else if (strict) {
return dn.parse(input)
function ensureDN (input) {
if (DN.isDn(input)) {
return DN
} else if (typeof (input) === 'string') {
return input
return DN.fromString(input)
} else {
throw new Error('invalid DN')
}
@ -137,7 +134,6 @@ function Client (options) {
failAfter: parseInt(rOpts.failAfter, 10) || Infinity
}
}
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true
this.queue = requestQueueFactory({
size: parseInt((options.queueSize || 0), 10),
@ -178,13 +174,13 @@ module.exports = Client
* The callback will be invoked as soon as the data is flushed out to the
* network, as there is never a response from abandon.
*
* @param {Number} messageID the messageID to abandon.
* @param {Number} messageId the messageId to abandon.
* @param {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err).
* @throws {TypeError} on invalid input.
*/
Client.prototype.abandon = function abandon (messageID, controls, callback) {
assert.number(messageID, 'messageID')
Client.prototype.abandon = function abandon (messageId, controls, callback) {
assert.number(messageId, 'messageId')
if (typeof (controls) === 'function') {
callback = controls
controls = []
@ -194,7 +190,7 @@ Client.prototype.abandon = function abandon (messageID, controls, callback) {
assert.func(callback, 'callback')
const req = new AbandonRequest({
abandonID: messageID,
abandonId: messageId,
controls: controls
})
@ -249,7 +245,7 @@ Client.prototype.add = function add (name, entry, controls, callback) {
}
const req = new AddRequest({
entry: ensureDN(name, this.strictDN),
entry: ensureDN(name),
attributes: entry,
controls: controls
})
@ -271,7 +267,12 @@ Client.prototype.bind = function bind (name,
controls,
callback,
_bypass) {
if (typeof (name) !== 'string' && !(name instanceof dn.DN)) { throw new TypeError('name (string) required') }
if (
typeof (name) !== 'string' &&
Object.prototype.toString.call(name) !== '[object LdapDn]'
) {
throw new TypeError('name (string) required')
}
assert.optionalString(credentials, 'credentials')
if (typeof (controls) === 'function') {
callback = controls
@ -326,7 +327,7 @@ Client.prototype.compare = function compare (name,
assert.func(callback, 'callback')
const req = new CompareRequest({
entry: ensureDN(name, this.strictDN),
entry: ensureDN(name),
attribute: attr,
value: value,
controls: controls
@ -358,7 +359,7 @@ Client.prototype.del = function del (name, controls, callback) {
assert.func(callback, 'callback')
const req = new DeleteRequest({
entry: ensureDN(name, this.strictDN),
entry: ensureDN(name),
controls: controls
})
@ -469,7 +470,7 @@ Client.prototype.modify = function modify (name, change, controls, callback) {
assert.func(callback, 'callback')
const req = new ModifyRequest({
object: ensureDN(name, this.strictDN),
object: ensureDN(name),
changes: changes,
controls: controls
})
@ -505,18 +506,16 @@ Client.prototype.modifyDN = function modifyDN (name,
}
assert.func(callback)
const DN = ensureDN(name)
// TODO: is non-strict handling desired here?
const newDN = dn.parse(newName)
const newDN = DN.fromString(newName)
const req = new ModifyDNRequest({
entry: DN,
entry: DN.fromString(name),
deleteOldRdn: true,
controls: controls
})
if (newDN.length !== 1) {
req.newRdn = dn.parse(newDN.rdns.shift().toString())
req.newRdn = DN.fromString(newDN.shift().toString())
req.newSuperior = newDN
} else {
req.newRdn = newDN
@ -594,7 +593,7 @@ Client.prototype.search = function search (base,
}
const self = this
const baseDN = ensureDN(base, this.strictDN)
const baseDN = ensureDN(base)
function sendRequest (ctrls, emitter, cb) {
const req = new SearchRequest({
@ -877,15 +876,46 @@ Client.prototype.connect = function connect () {
})
// The "router"
//
// This is invoked after the incoming BER has been parsed into a JavaScript
// object.
tracker.parser.on('message', function onMessage (message) {
message.connection = self._socket
const callback = tracker.fetch(message.messageID)
const { message: trackedMessage, callback } = tracker.fetch(message.messageId)
if (!callback) {
log.error({ message: message.json }, 'unsolicited message')
log.error({ message: message.pojo }, 'unsolicited message')
return false
}
// Some message types have narrower implementations and require extra
// parsing to be complete. In particular, ExtensionRequest messages will
// return responses that do not identify the request that generated them.
// Therefore, we have to match the response to the request and handle
// the extra processing accordingly.
switch (trackedMessage.type) {
case 'ExtensionRequest': {
const extensionType = ExtendedRequest.recognizedOIDs().lookupName(trackedMessage.requestName)
switch (extensionType) {
case 'PASSWORD_MODIFY': {
message = messages.PasswordModifyResponse.fromResponse(message)
break
}
case 'WHO_AM_I': {
message = messages.WhoAmIResponse.fromResponse(message)
break
}
default:
}
break
}
default:
}
return callback(message)
})
@ -1084,8 +1114,16 @@ Client.prototype._onClose = function _onClose (closeError) {
return cb(new ConnectionError(tracker.id + ' closed'))
} else {
// Unbinds will be communicated as a success since we're closed
const unbind = new UnbindResponse({ messageID: msgid })
unbind.status = 'unbind'
// TODO: we are faking this "UnbindResponse" object in order to make
// tests pass. There is no such thing as an "unbind response" in the LDAP
// protocol. When the client is revamped, this logic should be removed.
// ~ jsumners 2023-02-16
const Unbind = class extends LDAPResult {
messageID = msgid
messageId = msgid
status = 'unbind'
}
const unbind = new Unbind()
return cb(unbind)
}
})
@ -1203,21 +1241,23 @@ Client.prototype._sendSocket = function _sendSocket (message,
function messageCallback (msg) {
if (timer) { clearTimeout(timer) }
log.trace({ msg: msg ? msg.json : null }, 'response received')
log.trace({ msg: msg ? msg.pojo : null }, 'response received')
if (expect === 'abandon') { return sendResult('end', null) }
if (msg instanceof SearchEntry || msg instanceof SearchReference) {
let event = msg.constructor.name
event = event[0].toLowerCase() + event.slice(1)
// Generate the event name for the event emitter, i.e. "searchEntry"
// and "searchReference".
event = (event[0].toLowerCase() + event.slice(1)).replaceAll('Result', '')
return sendResult(event, msg)
} else {
tracker.remove(message.messageID)
tracker.remove(message.messageId)
// Potentially mark client as idle
self._updateIdle()
if (msg instanceof LDAPResult) {
if (expect.indexOf(msg.status) === -1) {
if (msg.status !== 0 && expect.indexOf(msg.status) === -1) {
return sendResult('error', errors.getError(msg))
}
return sendResult('end', msg)
@ -1231,7 +1271,7 @@ Client.prototype._sendSocket = function _sendSocket (message,
function onRequestTimeout () {
self.emit('timeout', message)
const cb = tracker.fetch(message.messageID)
const { callback: cb } = tracker.fetch(message.messageId)
if (cb) {
// FIXME: the timed-out request should be abandoned
cb(new errors.TimeoutError('request timeout (client interrupt)'))
@ -1240,8 +1280,8 @@ Client.prototype._sendSocket = function _sendSocket (message,
function writeCallback () {
if (expect === 'abandon') {
// Mark the messageID specified as abandoned
tracker.abandon(message.abandonID)
// Mark the messageId specified as abandoned
tracker.abandon(message.abandonId)
// No need to track the abandon request itself
tracker.remove(message.id)
return callback(null)
@ -1273,10 +1313,11 @@ Client.prototype._sendSocket = function _sendSocket (message,
timer = setTimeout(onRequestTimeout, self.timeout)
}
log.trace('sending request %j', message.json)
log.trace('sending request %j', message.pojo)
try {
return conn.write(message.toBer(), writeCallback)
const messageBer = message.toBer()
return conn.write(messageBer.buffer, writeCallback)
} catch (e) {
if (timer) { clearTimeout(timer) }

View File

@ -62,13 +62,23 @@ module.exports = function messageTrackerFactory (options) {
*/
tracker.abandon = function abandonMessage (msgID) {
if (messages.has(msgID) === false) return false
const toAbandon = messages.get(msgID)
abandoned.set(msgID, {
age: currentID,
cb: messages.get(msgID)
message: toAbandon.message,
cb: toAbandon.callback
})
return messages.delete(msgID)
}
/**
* @typedef {object} Tracked
* @property {object} message The tracked message. Usually the outgoing
* request object.
* @property {Function} callback The handler to use when receiving a
* response to the tracked message.
*/
/**
* Retrieves the message handler for a message. Removes abandoned messages
* that have been given time to be resolved.
@ -79,10 +89,10 @@ module.exports = function messageTrackerFactory (options) {
* @method fetch
*/
tracker.fetch = function fetchMessage (msgID) {
const messageCB = messages.get(msgID)
if (messageCB) {
const tracked = messages.get(msgID)
if (tracked) {
purgeAbandoned(msgID, abandoned)
return messageCB
return tracked
}
// We sent an abandon request but the server either wasn't able to process
@ -91,7 +101,7 @@ module.exports = function messageTrackerFactory (options) {
// to be processed normally.
const abandonedMsg = abandoned.get(msgID)
if (abandonedMsg) {
return abandonedMsg.cb
return { message: abandonedMsg, callback: abandonedMsg.cb }
}
return null
@ -110,7 +120,7 @@ module.exports = function messageTrackerFactory (options) {
messages.forEach((val, key) => {
purgeAbandoned(key, abandoned)
tracker.remove(key)
cb(key, val)
cb(key, val.callback)
})
}
@ -132,7 +142,7 @@ module.exports = function messageTrackerFactory (options) {
* Add a message handler to be tracked.
*
* @param {object} message The message object to be tracked. This object will
* have a new property added to it: `messageID`.
* have a new property added to it: `messageId`.
* @param {function} callback The handler for the message.
*
* @memberof MessageTracker
@ -143,8 +153,8 @@ module.exports = function messageTrackerFactory (options) {
// This side effect is not ideal but the client doesn't attach the tracker
// to itself until after the `.connect` method has fired. If this can be
// refactored later, then we can possibly get rid of this side effect.
message.messageID = currentID
messages.set(currentID, callback)
message.messageId = currentID
messages.set(currentID, { callback, message })
}
return tracker

View File

@ -2,13 +2,8 @@
const EventEmitter = require('events').EventEmitter
const util = require('util')
const assert = require('assert-plus')
// var dn = require('../dn')
// var messages = require('../messages/index')
const { PagedResultsControl } = require('@ldapjs/controls')
const CorkedEmitter = require('../corked_emitter.js')
/// --- API
@ -94,13 +89,13 @@ SearchPager.prototype._onEnd = function _onEnd (res) {
if (this.listeners('pageError').length > 0) {
this.emit('pageError', err)
// If the consumer as subscribed to pageError, SearchPager is absolved
// from deliverying the fault via the 'error' event. Emitting an 'end'
// from delivering the fault via the 'error' event. Emitting an 'end'
// event after 'error' breaks the contract that the standard client
// provides, so it's only a possibility if 'pageError' is used instead.
this.emit('end', res)
} else {
this.emit('error', err)
// No end event possible per explaination above.
// No end event possible per explanation above.
}
return
}

473
lib/dn.js
View File

@ -1,473 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
/// --- Helpers
function invalidDN (name) {
const e = new Error()
e.name = 'InvalidDistinguishedNameError'
e.message = name
return e
}
function isAlphaNumeric (c) {
const re = /[A-Za-z0-9]/
return re.test(c)
}
function isWhitespace (c) {
const re = /\s/
return re.test(c)
}
function repeatChar (c, n) {
let out = ''
const max = n || 0
for (let i = 0; i < max; i++) { out += c }
return out
}
/// --- API
function RDN (obj) {
const self = this
this.attrs = {}
if (obj) {
Object.keys(obj).forEach(function (k) {
self.set(k, obj[k])
})
}
}
RDN.prototype.set = function rdnSet (name, value, opts) {
assert.string(name, 'name (string) required')
assert.string(value, 'value (string) required')
const self = this
const lname = name.toLowerCase()
this.attrs[lname] = {
value: value,
name: name
}
if (opts && typeof (opts) === 'object') {
Object.keys(opts).forEach(function (k) {
if (k !== 'value') { self.attrs[lname][k] = opts[k] }
})
}
}
RDN.prototype.equals = function rdnEquals (rdn) {
if (typeof (rdn) !== 'object') { return false }
const ourKeys = Object.keys(this.attrs)
const theirKeys = Object.keys(rdn.attrs)
if (ourKeys.length !== theirKeys.length) { return false }
ourKeys.sort()
theirKeys.sort()
for (let i = 0; i < ourKeys.length; i++) {
if (ourKeys[i] !== theirKeys[i]) { return false }
if (this.attrs[ourKeys[i]].value !== rdn.attrs[ourKeys[i]].value) { return false }
}
return true
}
/**
* Convert RDN to string according to specified formatting options.
* (see: DN.format for option details)
*/
RDN.prototype.format = function rdnFormat (options) {
assert.optionalObject(options, 'options must be an object')
options = options || {}
const self = this
let str = ''
function escapeValue (val, forceQuote) {
let out = ''
let cur = 0
const len = val.length
let quoted = false
/* BEGIN JSSTYLED */
// TODO: figure out what this regex is actually trying to test for and
// fix it to appease the linter.
/* eslint-disable-next-line no-useless-escape */
const escaped = /[\\\"]/
const special = /[,=+<>#;]/
/* END JSSTYLED */
if (len > 0) {
// Wrap strings with trailing or leading spaces in quotes
quoted = forceQuote || (val[0] === ' ' || val[len - 1] === ' ')
}
while (cur < len) {
if (escaped.test(val[cur]) || (!quoted && special.test(val[cur]))) {
out += '\\'
}
out += val[cur++]
}
if (quoted) { out = '"' + out + '"' }
return out
}
function sortParsed (a, b) {
return self.attrs[a].order - self.attrs[b].order
}
function sortStandard (a, b) {
const nameCompare = a.localeCompare(b)
if (nameCompare === 0) {
// TODO: Handle binary values
return self.attrs[a].value.localeCompare(self.attrs[b].value)
} else {
return nameCompare
}
}
const keys = Object.keys(this.attrs)
if (options.keepOrder) {
keys.sort(sortParsed)
} else {
keys.sort(sortStandard)
}
keys.forEach(function (key) {
const attr = self.attrs[key]
if (str.length) { str += '+' }
if (options.keepCase) {
str += attr.name
} else {
if (options.upperName) { str += key.toUpperCase() } else { str += key }
}
str += '=' + escapeValue(attr.value, (options.keepQuote && attr.quoted))
})
return str
}
RDN.prototype.toString = function rdnToString () {
return this.format()
}
// Thank you OpenJDK!
function parse (name) {
if (typeof (name) !== 'string') { throw new TypeError('name (string) required') }
let cur = 0
const len = name.length
function parseRdn () {
const rdn = new RDN()
let order = 0
rdn.spLead = trim()
while (cur < len) {
const opts = {
order: order
}
const attr = parseAttrType()
trim()
if (cur >= len || name[cur++] !== '=') { throw invalidDN(name) }
trim()
// Parameters about RDN value are set in 'opts' by parseAttrValue
const value = parseAttrValue(opts)
rdn.set(attr, value, opts)
rdn.spTrail = trim()
if (cur >= len || name[cur] !== '+') { break }
++cur
++order
}
return rdn
}
function trim () {
let count = 0
while ((cur < len) && isWhitespace(name[cur])) {
++cur
count++
}
return count
}
function parseAttrType () {
const beg = cur
while (cur < len) {
const c = name[cur]
if (isAlphaNumeric(c) ||
c === '.' ||
c === '-' ||
c === ' ') {
++cur
} else {
break
}
}
// Back out any trailing spaces.
while ((cur > beg) && (name[cur - 1] === ' ')) { --cur }
if (beg === cur) { throw invalidDN(name) }
return name.slice(beg, cur)
}
function parseAttrValue (opts) {
if (cur < len && name[cur] === '#') {
opts.binary = true
return parseBinaryAttrValue()
} else if (cur < len && name[cur] === '"') {
opts.quoted = true
return parseQuotedAttrValue()
} else {
return parseStringAttrValue()
}
}
function parseBinaryAttrValue () {
const beg = cur++
while (cur < len && isAlphaNumeric(name[cur])) { ++cur }
return name.slice(beg, cur)
}
function parseQuotedAttrValue () {
let str = ''
++cur // Consume the first quote
while ((cur < len) && name[cur] !== '"') {
if (name[cur] === '\\') { cur++ }
str += name[cur++]
}
if (cur++ >= len) {
// no closing quote
throw invalidDN(name)
}
return str
}
function parseStringAttrValue () {
const beg = cur
let str = ''
let esc = -1
while ((cur < len) && !atTerminator()) {
if (name[cur] === '\\') {
// Consume the backslash and mark its place just in case it's escaping
// whitespace which needs to be preserved.
esc = cur++
}
if (cur === len) {
// backslash followed by nothing
throw invalidDN(name)
}
str += name[cur++]
}
// Trim off (unescaped) trailing whitespace and rewind cursor to the end of
// the AttrValue to record whitespace length.
for (; cur > beg; cur--) {
if (!isWhitespace(name[cur - 1]) || (esc === (cur - 1))) { break }
}
return str.slice(0, cur - beg)
}
function atTerminator () {
return (cur < len &&
(name[cur] === ',' ||
name[cur] === ';' ||
name[cur] === '+'))
}
const rdns = []
// Short-circuit for empty DNs
if (len === 0) { return new DN(rdns) }
rdns.push(parseRdn())
while (cur < len) {
if (name[cur] === ',' || name[cur] === ';') {
++cur
rdns.push(parseRdn())
} else {
throw invalidDN(name)
}
}
return new DN(rdns)
}
function DN (rdns) {
assert.optionalArrayOfObject(rdns, '[object] required')
this.rdns = rdns ? rdns.slice() : []
this._format = {}
}
Object.defineProperties(DN.prototype, {
length: {
get: function getLength () { return this.rdns.length },
configurable: false
}
})
/**
* Convert DN to string according to specified formatting options.
*
* Parameters:
* - options: formatting parameters (optional, details below)
*
* Options are divided into two types:
* - Preservation options: Using data recorded during parsing, details of the
* original DN are preserved when converting back into a string.
* - Modification options: Alter string formatting defaults.
*
* Preservation options _always_ take precedence over modification options.
*
* Preservation Options:
* - keepOrder: Order of multi-value RDNs.
* - keepQuote: RDN values which were quoted will remain so.
* - keepSpace: Leading/trailing spaces will be output.
* - keepCase: Parsed attr name will be output instead of lowercased version.
*
* Modification Options:
* - upperName: RDN names will be uppercased instead of lowercased.
* - skipSpace: Disable trailing space after RDN separators
*/
DN.prototype.format = function dnFormat (options) {
assert.optionalObject(options, 'options must be an object')
options = options || this._format
let str = ''
this.rdns.forEach(function (rdn) {
const rdnString = rdn.format(options)
if (str.length !== 0) {
str += ','
}
if (options.keepSpace) {
str += (repeatChar(' ', rdn.spLead) +
rdnString + repeatChar(' ', rdn.spTrail))
} else if (options.skipSpace === true || str.length === 0) {
str += rdnString
} else {
str += ' ' + rdnString
}
})
return str
}
/**
* Set default string formatting options.
*/
DN.prototype.setFormat = function setFormat (options) {
assert.object(options, 'options must be an object')
this._format = options
}
DN.prototype.toString = function dnToString () {
return this.format()
}
DN.prototype.parentOf = function parentOf (dn) {
if (typeof (dn) !== 'object') { dn = parse(dn) }
if (this.rdns.length >= dn.rdns.length) { return false }
const diff = dn.rdns.length - this.rdns.length
for (let i = this.rdns.length - 1; i >= 0; i--) {
const myRDN = this.rdns[i]
const theirRDN = dn.rdns[i + diff]
if (!myRDN.equals(theirRDN)) { return false }
}
return true
}
DN.prototype.childOf = function childOf (dn) {
if (typeof (dn) !== 'object') { dn = parse(dn) }
return dn.parentOf(this)
}
DN.prototype.isEmpty = function isEmpty () {
return (this.rdns.length === 0)
}
DN.prototype.equals = function dnEquals (dn) {
if (typeof (dn) !== 'object') { dn = parse(dn) }
if (this.rdns.length !== dn.rdns.length) { return false }
for (let i = 0; i < this.rdns.length; i++) {
if (!this.rdns[i].equals(dn.rdns[i])) { return false }
}
return true
}
DN.prototype.parent = function dnParent () {
if (this.rdns.length !== 0) {
const save = this.rdns.shift()
const dn = new DN(this.rdns)
this.rdns.unshift(save)
return dn
}
return null
}
DN.prototype.clone = function dnClone () {
const dn = new DN(this.rdns)
dn._format = this._format
return dn
}
DN.prototype.reverse = function dnReverse () {
this.rdns.reverse()
return this
}
DN.prototype.pop = function dnPop () {
return this.rdns.pop()
}
DN.prototype.push = function dnPush (rdn) {
assert.object(rdn, 'rdn (RDN) required')
return this.rdns.push(rdn)
}
DN.prototype.shift = function dnShift () {
return this.rdns.shift()
}
DN.prototype.unshift = function dnUnshift (rdn) {
assert.object(rdn, 'rdn (RDN) required')
return this.rdns.unshift(rdn)
}
DN.isDN = function isDN (dn) {
if (!dn || typeof (dn) !== 'object') {
return false
}
if (dn instanceof DN) {
return true
}
if (Array.isArray(dn.rdns)) {
// Really simple duck-typing for now
return true
}
return false
}
/// --- Exports
module.exports = {
parse: parse,
DN: DN,
RDN: RDN
}

View File

@ -3,14 +3,14 @@
const logger = require('./logger')
const client = require('./client')
const Attribute = require('./attribute')
const Change = require('./change')
const Attribute = require('@ldapjs/attribute')
const Change = require('@ldapjs/change')
const Protocol = require('@ldapjs/protocol')
const Server = require('./server')
const controls = require('./controls')
const persistentSearch = require('./persistent_search')
const dn = require('./dn')
const dn = require('@ldapjs/dn')
const errors = require('./errors')
const filters = require('@ldapjs/filter')
const messages = require('./messages')
@ -43,7 +43,7 @@ module.exports = {
dn: dn,
DN: dn.DN,
RDN: dn.RDN,
parseDN: dn.parse,
parseDN: dn.DN.fromString,
persistentSearch: persistentSearch,
PersistentSearchCache: persistentSearch.PersistentSearchCache,

View File

@ -1,87 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Protocol = require('@ldapjs/protocol')
/// --- API
function AbandonRequest (options) {
options = options || {}
assert.object(options)
assert.optionalNumber(options.abandonID)
options.protocolOp = Protocol.operations.LDAP_REQ_ABANDON
LDAPMessage.call(this, options)
this.abandonID = options.abandonID || 0
}
util.inherits(AbandonRequest, LDAPMessage)
Object.defineProperties(AbandonRequest.prototype, {
type: {
get: function getType () { return 'AbandonRequest' },
configurable: false
}
})
AbandonRequest.prototype._parse = function (ber, length) {
assert.ok(ber)
assert.ok(length)
// What a PITA - have to replicate ASN.1 integer logic to work around the
// way abandon is encoded and the way ldapjs framework handles "normal"
// messages
const buf = ber.buffer
let offset = 0
let value = 0
const fb = buf[offset++]
value = fb & 0x7F
for (let i = 1; i < length; i++) {
value <<= 8
value |= (buf[offset++] & 0xff)
}
if ((fb & 0x80) === 0x80) { value = -value }
ber._offset += length
this.abandonID = value
return true
}
AbandonRequest.prototype._toBer = function (ber) {
assert.ok(ber)
let i = this.abandonID
let sz = 4
while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000)) &&
(sz > 1)) {
sz--
i <<= 8
}
assert.ok(sz <= 4)
while (sz-- > 0) {
ber.writeByte((i & 0xff000000) >> 24)
i <<= 8
}
return ber
}
AbandonRequest.prototype._json = function (j) {
assert.ok(j)
j.abandonID = this.abandonID
return j
}
/// --- Exports
module.exports = AbandonRequest

View File

@ -1,33 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./result')
/// --- API
function AbandonResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = 0
LDAPMessage.call(this, options)
}
util.inherits(AbandonResponse, LDAPMessage)
Object.defineProperties(AbandonResponse.prototype, {
type: {
get: function getType () { return 'AbandonResponse' },
configurable: false
}
})
AbandonResponse.prototype.end = function (_status) {}
AbandonResponse.prototype._json = function (j) {
return j
}
/// --- Exports
module.exports = AbandonResponse

View File

@ -1,159 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Attribute = require('../attribute')
const Protocol = require('@ldapjs/protocol')
const lassert = require('../assert')
/// --- API
function AddRequest (options) {
options = options || {}
assert.object(options)
lassert.optionalStringDN(options.entry)
lassert.optionalArrayOfAttribute(options.attributes)
options.protocolOp = Protocol.operations.LDAP_REQ_ADD
LDAPMessage.call(this, options)
this.entry = options.entry || null
this.attributes = options.attributes ? options.attributes.slice(0) : []
}
util.inherits(AddRequest, LDAPMessage)
Object.defineProperties(AddRequest.prototype, {
type: {
get: function getType () { return 'AddRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.entry },
configurable: false
}
})
AddRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.entry = ber.readString()
ber.readSequence()
const end = ber.offset + ber.length
while (ber.offset < end) {
const a = new Attribute()
a.parse(ber)
a.type = a.type.toLowerCase()
if (a.type === 'objectclass') {
for (let i = 0; i < a.vals.length; i++) { a.vals[i] = a.vals[i].toLowerCase() }
}
this.attributes.push(a)
}
this.attributes.sort(Attribute.compare)
return true
}
AddRequest.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.entry.toString())
ber.startSequence()
this.attributes.forEach(function (a) {
a.toBer(ber)
})
ber.endSequence()
return ber
}
AddRequest.prototype._json = function (j) {
assert.ok(j)
j.entry = this.entry.toString()
j.attributes = []
this.attributes.forEach(function (a) {
j.attributes.push(a.json)
})
return j
}
AddRequest.prototype.indexOf = function (attr) {
if (!attr || typeof (attr) !== 'string') { throw new TypeError('attr (string) required') }
for (let i = 0; i < this.attributes.length; i++) {
if (this.attributes[i].type === attr) { return i }
}
return -1
}
AddRequest.prototype.attributeNames = function () {
const attrs = []
for (let i = 0; i < this.attributes.length; i++) { attrs.push(this.attributes[i].type.toLowerCase()) }
return attrs
}
AddRequest.prototype.getAttribute = function (name) {
if (!name || typeof (name) !== 'string') { throw new TypeError('attribute name (string) required') }
name = name.toLowerCase()
for (let i = 0; i < this.attributes.length; i++) {
if (this.attributes[i].type === name) { return this.attributes[i] }
}
return null
}
AddRequest.prototype.addAttribute = function (attr) {
if (!(attr instanceof Attribute)) { throw new TypeError('attribute (Attribute) required') }
return this.attributes.push(attr)
}
/**
* Returns a "pure" JS representation of this object.
*
* An example object would look like:
*
* {
* "dn": "cn=unit, dc=test",
* "attributes": {
* "cn": ["unit", "foo"],
* "objectclass": ["top", "person"]
* }
* }
*
* @return {Object} that looks like the above.
*/
AddRequest.prototype.toObject = function () {
const self = this
const obj = {
dn: self.entry ? self.entry.toString() : '',
attributes: {}
}
if (!this.attributes || !this.attributes.length) { return obj }
this.attributes.forEach(function (a) {
if (!obj.attributes[a.type]) { obj.attributes[a.type] = [] }
a.vals.forEach(function (v) {
if (obj.attributes[a.type].indexOf(v) === -1) { obj.attributes[a.type].push(v) }
})
})
return obj
}
/// --- Exports
module.exports = AddRequest

View File

@ -1,22 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('@ldapjs/protocol')
/// --- API
function AddResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.operations.LDAP_RES_ADD
LDAPResult.call(this, options)
}
util.inherits(AddResponse, LDAPResult)
/// --- Exports
module.exports = AddResponse

View File

@ -1,84 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('@ldapjs/asn1')
const LDAPMessage = require('./message')
const Protocol = require('@ldapjs/protocol')
/// --- Globals
const Ber = asn1.Ber
const LDAP_BIND_SIMPLE = 'simple'
// var LDAP_BIND_SASL = 'sasl'
/// --- API
function BindRequest (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.operations.LDAP_REQ_BIND
LDAPMessage.call(this, options)
this.version = options.version || 0x03
this.name = options.name || null
this.authentication = options.authentication || LDAP_BIND_SIMPLE
this.credentials = options.credentials || ''
}
util.inherits(BindRequest, LDAPMessage)
Object.defineProperties(BindRequest.prototype, {
type: {
get: function getType () { return 'BindRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.name },
configurable: false
}
})
BindRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.version = ber.readInt()
this.name = ber.readString()
const t = ber.peek()
// TODO add support for SASL et al
if (t !== Ber.Context) { throw new Error('authentication 0x' + t.toString(16) + ' not supported') }
this.authentication = LDAP_BIND_SIMPLE
this.credentials = ber.readString(Ber.Context)
return true
}
BindRequest.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeInt(this.version)
ber.writeString((this.name || '').toString())
// TODO add support for SASL et al
ber.writeString((this.credentials || ''), Ber.Context)
return ber
}
BindRequest.prototype._json = function (j) {
assert.ok(j)
j.version = this.version
j.name = this.name
j.authenticationType = this.authentication
j.credentials = this.credentials
return j
}
/// --- Exports
module.exports = BindRequest

View File

@ -1,22 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('@ldapjs/protocol')
/// --- API
function BindResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.operations.LDAP_RES_BIND
LDAPResult.call(this, options)
}
util.inherits(BindResponse, LDAPResult)
/// --- Exports
module.exports = BindResponse

View File

@ -1,74 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Protocol = require('@ldapjs/protocol')
const lassert = require('../assert')
/// --- API
function CompareRequest (options) {
options = options || {}
assert.object(options)
assert.optionalString(options.attribute)
assert.optionalString(options.value)
lassert.optionalStringDN(options.entry)
options.protocolOp = Protocol.operations.LDAP_REQ_COMPARE
LDAPMessage.call(this, options)
this.entry = options.entry || null
this.attribute = options.attribute || ''
this.value = options.value || ''
}
util.inherits(CompareRequest, LDAPMessage)
Object.defineProperties(CompareRequest.prototype, {
type: {
get: function getType () { return 'CompareRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.entry },
configurable: false
}
})
CompareRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.entry = ber.readString()
ber.readSequence()
this.attribute = ber.readString().toLowerCase()
this.value = ber.readString()
return true
}
CompareRequest.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.entry.toString())
ber.startSequence()
ber.writeString(this.attribute)
ber.writeString(this.value)
ber.endSequence()
return ber
}
CompareRequest.prototype._json = function (j) {
assert.ok(j)
j.entry = this.entry.toString()
j.attribute = this.attribute
j.value = this.value
return j
}
/// --- Exports
module.exports = CompareRequest

View File

@ -1,33 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('@ldapjs/protocol')
/// --- API
function CompareResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.operations.LDAP_RES_COMPARE
LDAPResult.call(this, options)
}
util.inherits(CompareResponse, LDAPResult)
CompareResponse.prototype.end = function (matches) {
let status = 0x06
if (typeof (matches) === 'boolean') {
if (!matches) { status = 0x05 } // Compare false
} else {
status = matches
}
return LDAPResult.prototype.end.call(this, status)
}
/// --- Exports
module.exports = CompareResponse

View File

@ -1,62 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Protocol = require('@ldapjs/protocol')
const lassert = require('../assert')
/// --- API
function DeleteRequest (options) {
options = options || {}
assert.object(options)
lassert.optionalStringDN(options.entry)
options.protocolOp = Protocol.operations.LDAP_REQ_DELETE
LDAPMessage.call(this, options)
this.entry = options.entry || null
}
util.inherits(DeleteRequest, LDAPMessage)
Object.defineProperties(DeleteRequest.prototype, {
type: {
get: function getType () { return 'DeleteRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.entry },
configurable: false
}
})
DeleteRequest.prototype._parse = function (ber, length) {
assert.ok(ber)
this.entry = ber.buffer.slice(0, length).toString('utf8')
ber._offset += ber.length
return true
}
DeleteRequest.prototype._toBer = function (ber) {
assert.ok(ber)
const buf = Buffer.from(this.entry.toString())
for (let i = 0; i < buf.length; i++) { ber.writeByte(buf[i]) }
return ber
}
DeleteRequest.prototype._json = function (j) {
assert.ok(j)
j.entry = this.entry
return j
}
/// --- Exports
module.exports = DeleteRequest

View File

@ -1,22 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('@ldapjs/protocol')
/// --- API
function DeleteResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.operations.LDAP_RES_DELETE
LDAPResult.call(this, options)
}
util.inherits(DeleteResponse, LDAPResult)
/// --- Exports
module.exports = DeleteResponse

View File

@ -1,117 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Protocol = require('@ldapjs/protocol')
/// --- API
function ExtendedRequest (options) {
options = options || {}
assert.object(options)
assert.optionalString(options.requestName)
if (options.requestValue &&
!(Buffer.isBuffer(options.requestValue) ||
typeof (options.requestValue) === 'string')) {
throw new TypeError('options.requestValue must be a buffer or a string')
}
options.protocolOp = Protocol.operations.LDAP_REQ_EXTENSION
LDAPMessage.call(this, options)
this.requestName = options.requestName || ''
this.requestValue = options.requestValue
if (Buffer.isBuffer(this.requestValue)) {
this.requestValueBuffer = this.requestValue
} else {
this.requestValueBuffer = Buffer.from(this.requestValue || '', 'utf8')
}
}
util.inherits(ExtendedRequest, LDAPMessage)
Object.defineProperties(ExtendedRequest.prototype, {
type: {
get: function getType () { return 'ExtendedRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.requestName },
configurable: false
},
name: {
get: function getName () { return this.requestName },
set: function setName (val) {
assert.string(val)
this.requestName = val
},
configurable: false
},
value: {
get: function getValue () { return this.requestValue },
set: function setValue (val) {
if (!(Buffer.isBuffer(val) || typeof (val) === 'string')) { throw new TypeError('value must be a buffer or a string') }
if (Buffer.isBuffer(val)) {
this.requestValueBuffer = val
} else {
this.requestValueBuffer = Buffer.from(val, 'utf8')
}
this.requestValue = val
},
configurable: false
},
valueBuffer: {
get: function getValueBuffer () {
return this.requestValueBuffer
},
set: function setValueBuffer (val) {
if (!Buffer.isBuffer(val)) { throw new TypeError('valueBuffer must be a buffer') }
this.value = val
},
configurable: false
}
})
ExtendedRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.requestName = ber.readString(0x80)
if (ber.peek() === 0x81) {
this.requestValueBuffer = ber.readString(0x81, true)
this.requestValue = this.requestValueBuffer.toString('utf8')
}
return true
}
ExtendedRequest.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.requestName, 0x80)
if (Buffer.isBuffer(this.requestValue)) {
ber.writeBuffer(this.requestValue, 0x81)
} else if (typeof (this.requestValue) === 'string') {
ber.writeString(this.requestValue, 0x81)
}
return ber
}
ExtendedRequest.prototype._json = function (j) {
assert.ok(j)
j.requestName = this.requestName
j.requestValue = (Buffer.isBuffer(this.requestValue))
? this.requestValue.toString('hex')
: this.requestValue
return j
}
/// --- Exports
module.exports = ExtendedRequest

View File

@ -1,86 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('@ldapjs/protocol')
/// --- API
function ExtendedResponse (options) {
options = options || {}
assert.object(options)
assert.optionalString(options.responseName)
assert.optionalString(options.responsevalue)
this.responseName = options.responseName || undefined
this.responseValue = options.responseValue || undefined
options.protocolOp = Protocol.operations.LDAP_RES_EXTENSION
LDAPResult.call(this, options)
}
util.inherits(ExtendedResponse, LDAPResult)
Object.defineProperties(ExtendedResponse.prototype, {
type: {
get: function getType () { return 'ExtendedResponse' },
configurable: false
},
_dn: {
get: function getDN () { return this.responseName },
configurable: false
},
name: {
get: function getName () { return this.responseName },
set: function setName (val) {
assert.string(val)
this.responseName = val
},
configurable: false
},
value: {
get: function getValue () { return this.responseValue },
set: function (val) {
assert.string(val)
this.responseValue = val
},
configurable: false
}
})
ExtendedResponse.prototype._parse = function (ber) {
assert.ok(ber)
if (!LDAPResult.prototype._parse.call(this, ber)) { return false }
if (ber.peek() === 0x8a) { this.responseName = ber.readString(0x8a) }
if (ber.peek() === 0x8b) { this.responseValue = ber.readString(0x8b) }
return true
}
ExtendedResponse.prototype._toBer = function (ber) {
assert.ok(ber)
if (!LDAPResult.prototype._toBer.call(this, ber)) { return false }
if (this.responseName) { ber.writeString(this.responseName, 0x8a) }
if (this.responseValue) { ber.writeString(this.responseValue, 0x8b) }
return ber
}
ExtendedResponse.prototype._json = function (j) {
assert.ok(j)
j = LDAPResult.prototype._json.call(this, j)
j.responseName = this.responseName
j.responseValue = this.responseValue
return j
}
/// --- Exports
module.exports = ExtendedResponse

View File

@ -1,61 +1,39 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const LDAPMessage = require('./message')
const LDAPResult = require('./result')
const messages = require('@ldapjs/messages')
const Parser = require('./parser')
const AbandonRequest = require('./abandon_request')
const AbandonResponse = require('./abandon_response')
const AddRequest = require('./add_request')
const AddResponse = require('./add_response')
const BindRequest = require('./bind_request')
const BindResponse = require('./bind_response')
const CompareRequest = require('./compare_request')
const CompareResponse = require('./compare_response')
const DeleteRequest = require('./del_request')
const DeleteResponse = require('./del_response')
const ExtendedRequest = require('./ext_request')
const ExtendedResponse = require('./ext_response')
const ModifyRequest = require('./modify_request')
const ModifyResponse = require('./modify_response')
const ModifyDNRequest = require('./moddn_request')
const ModifyDNResponse = require('./moddn_response')
const SearchRequest = require('./search_request')
const SearchEntry = require('./search_entry')
const SearchReference = require('./search_reference')
const SearchResponse = require('./search_response')
const UnbindRequest = require('./unbind_request')
const UnbindResponse = require('./unbind_response')
/// --- API
module.exports = {
LDAPMessage: LDAPMessage,
LDAPResult: LDAPResult,
LDAPMessage: messages.LdapMessage,
LDAPResult: messages.LdapResult,
Parser: Parser,
AbandonRequest: AbandonRequest,
AbandonResponse: AbandonResponse,
AddRequest: AddRequest,
AddResponse: AddResponse,
BindRequest: BindRequest,
BindResponse: BindResponse,
CompareRequest: CompareRequest,
CompareResponse: CompareResponse,
DeleteRequest: DeleteRequest,
DeleteResponse: DeleteResponse,
ExtendedRequest: ExtendedRequest,
ExtendedResponse: ExtendedResponse,
ModifyRequest: ModifyRequest,
ModifyResponse: ModifyResponse,
ModifyDNRequest: ModifyDNRequest,
ModifyDNResponse: ModifyDNResponse,
SearchRequest: SearchRequest,
SearchEntry: SearchEntry,
SearchReference: SearchReference,
AbandonRequest: messages.AbandonRequest,
AbandonResponse: messages.AbandonResponse,
AddRequest: messages.AddRequest,
AddResponse: messages.AddResponse,
BindRequest: messages.BindRequest,
BindResponse: messages.BindResponse,
CompareRequest: messages.CompareRequest,
CompareResponse: messages.CompareResponse,
DeleteRequest: messages.DeleteRequest,
DeleteResponse: messages.DeleteResponse,
ExtendedRequest: messages.ExtensionRequest,
ExtendedResponse: messages.ExtensionResponse,
ModifyRequest: messages.ModifyRequest,
ModifyResponse: messages.ModifyResponse,
ModifyDNRequest: messages.ModifyDnRequest,
ModifyDNResponse: messages.ModifyDnResponse,
SearchRequest: messages.SearchRequest,
SearchEntry: messages.SearchResultEntry,
SearchReference: messages.SearchResultReference,
SearchResponse: SearchResponse,
UnbindRequest: UnbindRequest,
UnbindResponse: UnbindResponse
UnbindRequest: messages.UnbindRequest
}

View File

@ -1,109 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('@ldapjs/asn1')
const logger = require('../logger')
// var Control = require('../controls').Control
/// --- Globals
// var Ber = asn1.Ber
// var BerReader = asn1.BerReader
const BerWriter = asn1.BerWriter
const getControl = require('../controls').getControl
/// --- API
/**
* LDAPMessage structure.
*
* @param {Object} options stuff.
*/
function LDAPMessage (options) {
assert.object(options)
this.messageID = options.messageID || 0
this.protocolOp = options.protocolOp || undefined
this.controls = options.controls ? options.controls.slice(0) : []
this.log = options.log || logger
}
Object.defineProperties(LDAPMessage.prototype, {
id: {
get: function getId () { return this.messageID },
configurable: false
},
dn: {
get: function getDN () { return this._dn || '' },
configurable: false
},
type: {
get: function getType () { return 'LDAPMessage' },
configurable: false
},
json: {
get: function () {
const out = this._json({
messageID: this.messageID,
protocolOp: this.type
})
out.controls = this.controls
return out
},
configurable: false
}
})
LDAPMessage.prototype.toString = function () {
return JSON.stringify(this.json)
}
LDAPMessage.prototype.parse = function (ber) {
assert.ok(ber)
this.log.trace('parse: data=%s', util.inspect(ber.buffer))
// Delegate off to the specific type to parse
this._parse(ber, ber.length)
// Look for controls
if (ber.peek() === 0xa0) {
ber.readSequence()
const end = ber.offset + ber.length
while (ber.offset < end) {
const c = getControl(ber)
if (c) { this.controls.push(c) }
}
}
this.log.trace('Parsing done: %j', this.json)
return true
}
LDAPMessage.prototype.toBer = function () {
let writer = new BerWriter()
writer.startSequence()
writer.writeInt(this.messageID)
writer.startSequence(this.protocolOp)
if (this._toBer) { writer = this._toBer(writer) }
writer.endSequence()
if (this.controls && this.controls.length) {
writer.startSequence(0xa0)
this.controls.forEach(function (c) {
c.toBer(writer)
})
writer.endSequence()
}
writer.endSequence()
return writer.buffer
}
/// --- Exports
module.exports = LDAPMessage

View File

@ -1,85 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Protocol = require('@ldapjs/protocol')
const dn = require('../dn')
const lassert = require('../assert')
/// --- API
function ModifyDNRequest (options) {
options = options || {}
assert.object(options)
assert.optionalBool(options.deleteOldRdn)
lassert.optionalStringDN(options.entry)
lassert.optionalDN(options.newRdn)
lassert.optionalDN(options.newSuperior)
options.protocolOp = Protocol.operations.LDAP_REQ_MODRDN
LDAPMessage.call(this, options)
this.entry = options.entry || null
this.newRdn = options.newRdn || null
this.deleteOldRdn = options.deleteOldRdn || true
this.newSuperior = options.newSuperior || null
}
util.inherits(ModifyDNRequest, LDAPMessage)
Object.defineProperties(ModifyDNRequest.prototype, {
type: {
get: function getType () { return 'ModifyDNRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.entry },
configurable: false
}
})
ModifyDNRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.entry = ber.readString()
this.newRdn = dn.parse(ber.readString())
this.deleteOldRdn = ber.readBoolean()
if (ber.peek() === 0x80) { this.newSuperior = dn.parse(ber.readString(0x80)) }
return true
}
ModifyDNRequest.prototype._toBer = function (ber) {
// assert.ok(ber);
ber.writeString(this.entry.toString())
ber.writeString(this.newRdn.toString())
ber.writeBoolean(this.deleteOldRdn)
if (this.newSuperior) {
const s = this.newSuperior.toString()
const len = Buffer.byteLength(s)
ber.writeByte(0x80) // MODIFY_DN_REQUEST_NEW_SUPERIOR_TAG
ber.writeLength(len)
ber._ensure(len)
ber._buf.write(s, ber._offset)
ber._offset += len
}
return ber
}
ModifyDNRequest.prototype._json = function (j) {
assert.ok(j)
j.entry = this.entry.toString()
j.newRdn = this.newRdn.toString()
j.deleteOldRdn = this.deleteOldRdn
j.newSuperior = this.newSuperior ? this.newSuperior.toString() : ''
return j
}
/// --- Exports
module.exports = ModifyDNRequest

View File

@ -1,22 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('@ldapjs/protocol')
/// --- API
function ModifyDNResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.operations.LDAP_RES_MODRDN
LDAPResult.call(this, options)
}
util.inherits(ModifyDNResponse, LDAPResult)
/// --- Exports
module.exports = ModifyDNResponse

View File

@ -1,83 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const Change = require('../change')
const Protocol = require('@ldapjs/protocol')
const lassert = require('../assert')
/// --- API
function ModifyRequest (options) {
options = options || {}
assert.object(options)
lassert.optionalStringDN(options.object)
lassert.optionalArrayOfAttribute(options.attributes)
options.protocolOp = Protocol.operations.LDAP_REQ_MODIFY
LDAPMessage.call(this, options)
this.object = options.object || null
this.changes = options.changes ? options.changes.slice(0) : []
}
util.inherits(ModifyRequest, LDAPMessage)
Object.defineProperties(ModifyRequest.prototype, {
type: {
get: function getType () { return 'ModifyRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.object },
configurable: false
}
})
ModifyRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.object = ber.readString()
ber.readSequence()
const end = ber.offset + ber.length
while (ber.offset < end) {
const c = new Change()
c.parse(ber)
c.modification.type = c.modification.type.toLowerCase()
this.changes.push(c)
}
this.changes.sort(Change.compare)
return true
}
ModifyRequest.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeString(this.object.toString())
ber.startSequence()
this.changes.forEach(function (c) {
c.toBer(ber)
})
ber.endSequence()
return ber
}
ModifyRequest.prototype._json = function (j) {
assert.ok(j)
j.object = this.object
j.changes = []
this.changes.forEach(function (c) {
j.changes.push(c.json)
})
return j
}
/// --- Exports
module.exports = ModifyRequest

View File

@ -1,22 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const Protocol = require('@ldapjs/protocol')
/// --- API
function ModifyResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.operations.LDAP_RES_MODIFY
LDAPResult.call(this, options)
}
util.inherits(ModifyResponse, LDAPResult)
/// --- Exports
module.exports = ModifyResponse

View File

@ -5,39 +5,35 @@ const util = require('util')
const assert = require('assert-plus')
const asn1 = require('@ldapjs/asn1')
// var VError = require('verror').VError
const logger = require('../logger')
const AbandonRequest = require('./abandon_request')
const AddRequest = require('./add_request')
const AddResponse = require('./add_response')
const BindRequest = require('./bind_request')
const BindResponse = require('./bind_response')
const CompareRequest = require('./compare_request')
const CompareResponse = require('./compare_response')
const DeleteRequest = require('./del_request')
const DeleteResponse = require('./del_response')
const ExtendedRequest = require('./ext_request')
const ExtendedResponse = require('./ext_response')
const ModifyRequest = require('./modify_request')
const ModifyResponse = require('./modify_response')
const ModifyDNRequest = require('./moddn_request')
const ModifyDNResponse = require('./moddn_response')
const SearchRequest = require('./search_request')
const SearchEntry = require('./search_entry')
const SearchReference = require('./search_reference')
const messages = require('@ldapjs/messages')
const AbandonRequest = messages.AbandonRequest
const AddRequest = messages.AddRequest
const AddResponse = messages.AddResponse
const BindRequest = messages.BindRequest
const BindResponse = messages.BindResponse
const CompareRequest = messages.CompareRequest
const CompareResponse = messages.CompareResponse
const DeleteRequest = messages.DeleteRequest
const DeleteResponse = messages.DeleteResponse
const ExtendedRequest = messages.ExtensionRequest
const ExtendedResponse = messages.ExtensionResponse
const ModifyRequest = messages.ModifyRequest
const ModifyResponse = messages.ModifyResponse
const ModifyDNRequest = messages.ModifyDnRequest
const ModifyDNResponse = messages.ModifyDnResponse
const SearchRequest = messages.SearchRequest
const SearchEntry = messages.SearchResultEntry
const SearchReference = messages.SearchResultReference
const SearchResponse = require('./search_response')
const UnbindRequest = require('./unbind_request')
// var UnbindResponse = require('./unbind_response')
const LDAPResult = require('./result')
// var Message = require('./message')
const UnbindRequest = messages.UnbindRequest
const LDAPResult = messages.LdapResult
const Protocol = require('@ldapjs/protocol')
/// --- Globals
// var Ber = asn1.Ber
const BerReader = asn1.BerReader
/// --- API
@ -52,6 +48,13 @@ function Parser (options = {}) {
}
util.inherits(Parser, EventEmitter)
/**
* The LDAP server/client implementations will receive data from a stream and feed
* it into this method. This method will collect that data into an internal
* growing buffer. As that buffer fills with enough data to constitute a valid
* LDAP message, the data will be parsed, emitted as a message object, and
* reset the buffer to account for any next message in the stream.
*/
Parser.prototype.write = function (data) {
if (!data || !Buffer.isBuffer(data)) { throw new TypeError('data (buffer) required') }
@ -64,9 +67,9 @@ Parser.prototype.write = function (data) {
return true
}
self.buffer = (self.buffer ? Buffer.concat([self.buffer, data]) : data)
self.buffer = self.buffer ? Buffer.concat([self.buffer, data]) : data
const ber = new BerReader(self.buffer)
let ber = new BerReader(self.buffer)
let foundSeq = false
try {
@ -80,9 +83,22 @@ Parser.prototype.write = function (data) {
return false
} else if (ber.remain > ber.length) {
// ETOOMUCH
// This is sort of ugly, but allows us to make miminal copies
// This is an odd branch. Basically, it is setting `nextMessage` to
// a buffer that represents data part of a message subsequent to the one
// being processed. It then re-creates `ber` as a representation of
// the message being processed and advances its offset to the value
// position of the TLV.
// Set `nextMessage` to the bytes subsequent to the current message's
// value bytes. That is, slice from the byte immediately following the
// current message's value bytes until the end of the buffer.
nextMessage = self.buffer.slice(ber.offset + ber.length)
ber._size = ber.offset + ber.length
const currOffset = ber.offset
ber = new BerReader(ber.buffer.subarray(0, currOffset + ber.length))
ber.readSequence()
assert.equal(ber.remain, ber.length)
}
@ -92,13 +108,25 @@ Parser.prototype.write = function (data) {
let message
try {
// Bail here if peer isn't speaking protocol at all
message = this.getMessage(ber)
if (Object.prototype.toString.call(ber) === '[object BerReader]') {
// Parse the BER into a JavaScript object representation. The message
// objects require the full sequence in order to construct the object.
// At this point, we have already read the sequence tag and length, so
// we need to rewind the buffer a bit. The `.sequenceToReader` method
// does this for us.
message = messages.LdapMessage.parse(ber.sequenceToReader())
} else {
// Bail here if peer isn't speaking protocol at all
message = this.getMessage(ber)
}
if (!message) {
return end()
}
message.parse(ber)
// TODO: find a better way to handle logging now that messages and the
// server are decoupled. ~ jsumners 2023-02-17
message.log = this.log
} catch (e) {
this.emit('error', e, message)
return false
@ -113,7 +141,7 @@ Parser.prototype.getMessage = function (ber) {
const self = this
const messageID = ber.readInt()
const messageId = ber.readInt()
const type = ber.readSequence()
let Message
@ -203,7 +231,7 @@ Parser.prototype.getMessage = function (ber) {
new Error('Op 0x' + (type ? type.toString(16) : '??') +
' not supported'),
new LDAPResult({
messageID: messageID,
messageId: messageId,
protocolOp: type || Protocol.operations.LDAP_RES_EXTENSION
}))
@ -211,7 +239,7 @@ Parser.prototype.getMessage = function (ber) {
}
return new Message({
messageID: messageID,
messageId: messageId,
log: self.log
})
}

View File

@ -1,121 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
// var asn1 = require('@ldapjs/asn1')
const dtrace = require('../dtrace')
const LDAPMessage = require('./message')
const Protocol = require('@ldapjs/protocol')
/// --- Globals
// var Ber = asn1.Ber
// var BerWriter = asn1.BerWriter
/// --- API
function LDAPResult (options) {
options = options || {}
assert.object(options)
assert.optionalNumber(options.status)
assert.optionalString(options.matchedDN)
assert.optionalString(options.errorMessage)
assert.optionalArrayOfString(options.referrals)
LDAPMessage.call(this, options)
this.status = options.status || 0 // LDAP SUCCESS
this.matchedDN = options.matchedDN || ''
this.errorMessage = options.errorMessage || ''
this.referrals = options.referrals || []
this.connection = options.connection || null
}
util.inherits(LDAPResult, LDAPMessage)
Object.defineProperties(LDAPResult.prototype, {
type: {
get: function getType () { return 'LDAPResult' },
configurable: false
}
})
LDAPResult.prototype.end = function (status) {
assert.ok(this.connection)
if (typeof (status) === 'number') { this.status = status }
const ber = this.toBer()
this.log.debug('%s: sending: %j', this.connection.ldap.id, this.json)
try {
const self = this
this.connection.write(ber)
if (self._dtraceOp && self._dtraceId) {
dtrace.fire('server-' + self._dtraceOp + '-done', function () {
const c = self.connection || { ldap: {} }
return [
self._dtraceId || 0,
(c.remoteAddress || ''),
c.ldap.bindDN ? c.ldap.bindDN.toString() : '',
(self.requestDN ? self.requestDN.toString() : ''),
status || self.status,
self.errorMessage
]
})
}
} catch (e) {
this.log.warn(e, '%s failure to write message %j',
this.connection.ldap.id, this.json)
}
}
LDAPResult.prototype._parse = function (ber) {
assert.ok(ber)
this.status = ber.readEnumeration()
this.matchedDN = ber.readString()
this.errorMessage = ber.readString()
const t = ber.peek()
if (t === Protocol.operations.LDAP_RES_REFERRAL) {
const end = ber.offset + ber.length
while (ber.offset < end) { this.referrals.push(ber.readString()) }
}
return true
}
LDAPResult.prototype._toBer = function (ber) {
assert.ok(ber)
ber.writeEnumeration(this.status)
ber.writeString(this.matchedDN || '')
ber.writeString(this.errorMessage || '')
if (this.referrals.length) {
ber.startSequence(Protocol.operations.LDAP_RES_REFERRAL)
ber.writeStringArray(this.referrals)
ber.endSequence()
}
return ber
}
LDAPResult.prototype._json = function (j) {
assert.ok(j)
j.status = this.status
j.matchedDN = this.matchedDN
j.errorMessage = this.errorMessage
j.referrals = this.referrals
return j
}
/// --- Exports
module.exports = LDAPResult

View File

@ -1,188 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
// var asn1 = require('@ldapjs/asn1')
const LDAPMessage = require('./message')
const Attribute = require('../attribute')
const Protocol = require('@ldapjs/protocol')
const lassert = require('../assert')
/// --- Globals
// var BerWriter = asn1.BerWriter
/// --- API
function SearchEntry (options) {
options = options || {}
assert.object(options)
lassert.optionalStringDN(options.objectName)
options.protocolOp = Protocol.operations.LDAP_RES_SEARCH_ENTRY
LDAPMessage.call(this, options)
this.objectName = options.objectName || null
this.setAttributes(options.attributes || [])
}
util.inherits(SearchEntry, LDAPMessage)
Object.defineProperties(SearchEntry.prototype, {
type: {
get: function getType () { return 'SearchEntry' },
configurable: false
},
_dn: {
get: function getDN () { return this.objectName },
configurable: false
},
object: {
get: function getObject () {
const obj = {
dn: this.dn.toString(),
controls: []
}
this.attributes.forEach(function (a) {
if (a.vals && a.vals.length) {
if (a.vals.length > 1) {
obj[a.type] = a.vals.slice()
} else {
obj[a.type] = a.vals[0]
}
} else {
obj[a.type] = []
}
})
this.controls.forEach(function (element) {
obj.controls.push(element.json)
})
return obj
},
configurable: false
},
raw: {
get: function getRaw () {
const obj = {
dn: this.dn.toString(),
controls: []
}
this.attributes.forEach(function (a) {
if (a.buffers && a.buffers.length) {
if (a.buffers.length > 1) {
obj[a.type] = a.buffers.slice()
} else {
obj[a.type] = a.buffers[0]
}
} else {
obj[a.type] = []
}
})
this.controls.forEach(function (element) {
obj.controls.push(element.json)
})
return obj
},
configurable: false
}
})
SearchEntry.prototype.addAttribute = function (attr) {
if (!attr || typeof (attr) !== 'object') { throw new TypeError('attr (attribute) required') }
this.attributes.push(attr)
}
SearchEntry.prototype.toObject = function () {
return this.object
}
SearchEntry.prototype.fromObject = function (obj) {
if (typeof (obj) !== 'object') { throw new TypeError('object required') }
const self = this
if (obj.controls) { this.controls = obj.controls }
if (obj.attributes) { obj = obj.attributes }
this.attributes = []
Object.keys(obj).forEach(function (k) {
self.attributes.push(new Attribute({ type: k, vals: obj[k] }))
})
return true
}
SearchEntry.prototype.setAttributes = function (obj) {
if (typeof (obj) !== 'object') { throw new TypeError('object required') }
if (Array.isArray(obj)) {
obj.forEach(function (a) {
if (!Attribute.isAttribute(a)) { throw new TypeError('entry must be an Array of Attributes') }
})
this.attributes = obj
} else {
const self = this
self.attributes = []
Object.keys(obj).forEach(function (k) {
const attr = new Attribute({ type: k })
if (Array.isArray(obj[k])) {
obj[k].forEach(function (v) {
attr.addValue(v.toString())
})
} else {
attr.addValue(obj[k].toString())
}
self.attributes.push(attr)
})
}
}
SearchEntry.prototype._json = function (j) {
assert.ok(j)
j.objectName = this.objectName.toString()
j.attributes = []
this.attributes.forEach(function (a) {
j.attributes.push(a.json || a)
})
return j
}
SearchEntry.prototype._parse = function (ber) {
assert.ok(ber)
this.objectName = ber.readString()
assert.ok(ber.readSequence())
const end = ber.offset + ber.length
while (ber.offset < end) {
const a = new Attribute()
a.parse(ber)
this.attributes.push(a)
}
return true
}
SearchEntry.prototype._toBer = function (ber) {
assert.ok(ber)
const formattedObjectName = this.objectName.format({ skipSpace: true })
ber.writeString(formattedObjectName)
ber.startSequence()
this.attributes.forEach(function (a) {
// This may or may not be an attribute
ber = Attribute.toBer(a, ber)
})
ber.endSequence()
return ber
}
/// --- Exports
module.exports = SearchEntry

View File

@ -1,101 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
// var asn1 = require('@ldapjs/asn1')
const LDAPMessage = require('./message')
const Protocol = require('@ldapjs/protocol')
const dn = require('../dn')
const url = require('../url')
/// --- Globals
// var BerWriter = asn1.BerWriter
const parseURL = url.parse
/// --- API
function SearchReference (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.operations.LDAP_RES_SEARCH_REF
LDAPMessage.call(this, options)
this.uris = options.uris || []
}
util.inherits(SearchReference, LDAPMessage)
Object.defineProperties(SearchReference.prototype, {
type: {
get: function getType () { return 'SearchReference' },
configurable: false
},
_dn: {
get: function getDN () { return new dn.DN('') },
configurable: false
},
object: {
get: function getObject () {
return {
dn: this.dn.toString(),
uris: this.uris.slice()
}
},
configurable: false
},
urls: {
get: function getUrls () { return this.uris },
set: function setUrls (val) {
assert.ok(val)
assert.ok(Array.isArray(val))
this.uris = val.slice()
},
configurable: false
}
})
SearchReference.prototype.toObject = function () {
return this.object
}
SearchReference.prototype.fromObject = function (obj) {
if (typeof (obj) !== 'object') { throw new TypeError('object required') }
this.uris = obj.uris ? obj.uris.slice() : []
return true
}
SearchReference.prototype._json = function (j) {
assert.ok(j)
j.uris = this.uris.slice()
return j
}
SearchReference.prototype._parse = function (ber, length) {
assert.ok(ber)
while (ber.offset < length) {
const _url = ber.readString()
parseURL(_url)
this.uris.push(_url)
}
return true
}
SearchReference.prototype._toBer = function (ber) {
assert.ok(ber)
this.uris.forEach(function (u) {
ber.writeString(u.href || u)
})
return ber
}
/// --- Exports
module.exports = SearchReference

View File

@ -1,153 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const asn1 = require('@ldapjs/asn1')
const LDAPMessage = require('./message')
// var LDAPResult = require('./result')
const dn = require('../dn')
const filters = require('@ldapjs/filter')
const Protocol = require('@ldapjs/protocol')
/// --- Globals
const Ber = asn1.Ber
/// --- API
function SearchRequest (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.operations.LDAP_REQ_SEARCH
LDAPMessage.call(this, options)
if (options.baseObject !== undefined) {
this.baseObject = options.baseObject
} else {
this.baseObject = dn.parse('')
}
this.scope = options.scope || 'base'
this.derefAliases = options.derefAliases || Protocol.search.NEVER_DEREF_ALIASES
this.sizeLimit = options.sizeLimit || 0
this.timeLimit = options.timeLimit || 0
this.typesOnly = options.typesOnly || false
this.filter = options.filter || null
this.attributes = options.attributes ? options.attributes.slice(0) : []
}
util.inherits(SearchRequest, LDAPMessage)
Object.defineProperties(SearchRequest.prototype, {
type: {
get: function getType () { return 'SearchRequest' },
configurable: false
},
_dn: {
get: function getDN () { return this.baseObject },
configurable: false
},
scope: {
get: function getScope () {
switch (this._scope) {
case Protocol.search.SCOPE_BASE_OBJECT: return 'base'
case Protocol.search.SCOPE_ONE_LEVEL: return 'one'
case Protocol.search.SCOPE_SUBTREE: return 'sub'
default:
throw new Error(this._scope + ' is an invalid search scope')
}
},
set: function setScope (val) {
if (typeof (val) === 'string') {
switch (val) {
case 'base':
this._scope = Protocol.search.SCOPE_BASE_OBJECT
break
case 'one':
this._scope = Protocol.search.SCOPE_ONE_LEVEL
break
case 'sub':
this._scope = Protocol.search.SCOPE_SUBTREE
break
default:
throw new Error(val + ' is an invalid search scope')
}
} else {
this._scope = val
}
},
configurable: false
}
})
SearchRequest.prototype._parse = function (ber) {
assert.ok(ber)
this.baseObject = ber.readString()
this.scope = ber.readEnumeration()
this.derefAliases = ber.readEnumeration()
this.sizeLimit = ber.readInt()
this.timeLimit = ber.readInt()
this.typesOnly = ber.readBoolean()
this.filter = filters.parseBer(ber)
// look for attributes
if (ber.peek() === 0x30) {
ber.readSequence()
const end = ber.offset + ber.length
while (ber.offset < end) { this.attributes.push(ber.readString().toLowerCase()) }
}
return true
}
SearchRequest.prototype._toBer = function (ber) {
assert.ok(ber)
// Format only with commas, since that is what RFC 4514 mandates.
// There's a gotcha here: even though it's called baseObject,
// it can be a string or a DN object.
const formattedDN = dn.DN.isDN(this.baseObject)
? this.baseObject.format({ skipSpace: true })
: this.baseObject.toString()
ber.writeString(formattedDN)
ber.writeEnumeration(this._scope)
ber.writeEnumeration(this.derefAliases)
ber.writeInt(this.sizeLimit)
ber.writeInt(this.timeLimit)
ber.writeBoolean(this.typesOnly)
const f = this.filter || new filters.PresenceFilter({ attribute: 'objectclass' })
const filterBer = f.toBer(ber)
ber.appendBuffer(filterBer.buffer)
ber.startSequence(Ber.Sequence | Ber.Constructor)
if (this.attributes && this.attributes.length) {
this.attributes.forEach(function (a) {
ber.writeString(a)
})
}
ber.endSequence()
return ber
}
SearchRequest.prototype._json = function (j) {
assert.ok(j)
j.baseObject = this.baseObject
j.scope = this.scope
j.derefAliases = this.derefAliases
j.sizeLimit = this.sizeLimit
j.timeLimit = this.timeLimit
j.typesOnly = this.typesOnly
j.filter = this.filter.toString()
j.attributes = this.attributes
return j
}
/// --- Exports
module.exports = SearchRequest

View File

@ -1,31 +1,31 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPResult = require('./result')
const SearchEntry = require('./search_entry')
const SearchReference = require('./search_reference')
const Attribute = require('@ldapjs/attribute')
const {
SearchResultEntry: SearchEntry,
SearchResultReference: SearchReference,
SearchResultDone
} = require('@ldapjs/messages')
const dtrace = require('../dtrace')
const parseDN = require('../dn').parse
const parseURL = require('../url').parse
const Protocol = require('@ldapjs/protocol')
const parseDN = require('@ldapjs/dn').DN.fromString
/// --- API
function SearchResponse (options) {
options = options || {}
assert.object(options)
class SearchResponse extends SearchResultDone {
attributes
notAttributes
sentEntries
options.protocolOp = Protocol.operations.LDAP_RES_SEARCH
LDAPResult.call(this, options)
constructor (options = {}) {
super(options)
this.attributes = options.attributes ? options.attributes.slice() : []
this.notAttributes = []
this.sentEntries = 0
this.attributes = options.attributes ? options.attributes.slice() : []
this.notAttributes = []
this.sentEntries = 0
}
}
util.inherits(SearchResponse, LDAPResult)
/**
* Allows you to send a SearchEntry back to the client.
@ -44,12 +44,16 @@ SearchResponse.prototype.send = function (entry, nofiltering) {
const savedAttrs = {}
let save = null
if (entry instanceof SearchEntry || entry instanceof SearchReference) {
if (!entry.messageID) { entry.messageID = this.messageID }
if (entry.messageID !== this.messageID) { throw new Error('SearchEntry messageID mismatch') }
if (!entry.messageId) { entry.messageId = this.messageId }
if (entry.messageId !== this.messageId) {
throw new Error('SearchEntry messageId mismatch')
}
} else {
if (!entry.attributes) { throw new Error('entry.attributes required') }
const all = (self.attributes.indexOf('*') !== -1)
// Filter attributes in a plain object according to the magic `_` prefix
// and presence in `notAttributes`.
Object.keys(entry.attributes).forEach(function (a) {
const _a = a.toLowerCase()
if (!nofiltering && _a.length && _a[0] === '_') {
@ -69,39 +73,24 @@ SearchResponse.prototype.send = function (entry, nofiltering) {
save = entry
entry = new SearchEntry({
objectName: typeof (save.dn) === 'string' ? parseDN(save.dn) : save.dn,
messageID: self.messageID,
log: self.log
messageId: self.messageId,
attributes: Attribute.fromObject(entry.attributes)
})
entry.fromObject(save)
}
try {
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.json)
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.pojo)
this.connection.write(entry.toBer())
this.connection.write(entry.toBer().buffer)
this.sentEntries++
if (self._dtraceOp && self._dtraceId) {
dtrace.fire('server-search-entry', function () {
const c = self.connection || { ldap: {} }
return [
self._dtraceId || 0,
(c.remoteAddress || ''),
c.ldap.bindDN ? c.ldap.bindDN.toString() : '',
(self.requestDN ? self.requestDN.toString() : ''),
entry.objectName.toString(),
entry.attributes.length
]
})
}
// Restore attributes
Object.keys(savedAttrs).forEach(function (k) {
save.attributes[k] = savedAttrs[k]
})
} catch (e) {
this.log.warn(e, '%s failure to write message %j',
this.connection.ldap.id, this.json)
this.connection.ldap.id, this.pojo)
}
}
@ -109,11 +98,10 @@ SearchResponse.prototype.createSearchEntry = function (object) {
assert.object(object)
const entry = new SearchEntry({
messageID: this.messageID,
log: this.log,
objectName: object.objectName || object.dn
messageId: this.messageId,
objectName: object.objectName || object.dn,
attributes: object.attributes ?? []
})
entry.fromObject((object.attributes || object))
return entry
}
@ -122,15 +110,10 @@ SearchResponse.prototype.createSearchReference = function (uris) {
if (!Array.isArray(uris)) { uris = [uris] }
for (let i = 0; i < uris.length; i++) {
if (typeof (uris[i]) === 'string') { uris[i] = parseURL(uris[i]) }
}
const self = this
return new SearchReference({
messageID: self.messageID,
log: self.log,
uris: uris
messageId: self.messageId,
uri: uris
})
}

View File

@ -1,62 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const LDAPMessage = require('./message')
const dn = require('../dn')
const Protocol = require('@ldapjs/protocol')
/// --- Globals
const DN = dn.DN
const RDN = dn.RDN
/// --- API
function UnbindRequest (options) {
options = options || {}
assert.object(options)
options.protocolOp = Protocol.operations.LDAP_REQ_UNBIND
LDAPMessage.call(this, options)
}
util.inherits(UnbindRequest, LDAPMessage)
Object.defineProperties(UnbindRequest.prototype, {
type: {
get: function getType () { return 'UnbindRequest' },
configurable: false
},
_dn: {
get: function getDN () {
if (this.connection) {
return this.connection.ldap.bindDN
} else {
return new DN([new RDN({ cn: 'anonymous' })])
}
},
configurable: false
}
})
UnbindRequest.prototype._parse = function (ber) {
assert.ok(ber)
return true
}
UnbindRequest.prototype._toBer = function (ber) {
assert.ok(ber)
return ber
}
UnbindRequest.prototype._json = function (j) {
assert.ok(j)
return j
}
/// --- Exports
module.exports = UnbindRequest

View File

@ -1,64 +0,0 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
const assert = require('assert-plus')
const util = require('util')
const dtrace = require('../dtrace')
const LDAPMessage = require('./result')
/// --- API
// Ok, so there's really no such thing as an unbind 'response', but to make
// the framework not suck, I just made this up, and have it stubbed so it's
// not such a one-off.
function UnbindResponse (options) {
options = options || {}
assert.object(options)
options.protocolOp = 0
LDAPMessage.call(this, options)
}
util.inherits(UnbindResponse, LDAPMessage)
Object.defineProperties(UnbindResponse.prototype, {
type: {
get: function getType () { return 'UnbindResponse' },
configurable: false
}
})
/**
* Special override that just ends the connection, if present.
*
* @param {Number} _status completely ignored.
*/
UnbindResponse.prototype.end = function (_status) {
assert.ok(this.connection)
this.log.trace('%s: unbinding!', this.connection.ldap.id)
this.connection.end()
const self = this
if (self._dtraceOp && self._dtraceId) {
dtrace.fire('server-' + self._dtraceOp + '-done', function () {
const c = self.connection || { ldap: {} }
return [
self._dtraceId || 0,
(c.remoteAddress || ''),
c.ldap.bindDN ? c.ldap.bindDN.toString() : '',
(self.requestDN ? self.requestDN.toString() : ''),
0,
''
]
})
}
}
UnbindResponse.prototype._json = function (j) {
return j
}
/// --- Exports
module.exports = UnbindResponse

View File

@ -9,30 +9,31 @@ const util = require('util')
// var asn1 = require('@ldapjs/asn1')
const VError = require('verror').VError
const dn = require('./dn')
const { DN, RDN } = require('@ldapjs/dn')
const dtrace = require('./dtrace')
const errors = require('./errors')
const Protocol = require('@ldapjs/protocol')
const messages = require('@ldapjs/messages')
const Parser = require('./messages').Parser
const AbandonResponse = require('./messages/abandon_response')
const AddResponse = require('./messages/add_response')
const BindResponse = require('./messages/bind_response')
const CompareResponse = require('./messages/compare_response')
const DeleteResponse = require('./messages/del_response')
const ExtendedResponse = require('./messages/ext_response')
// var LDAPResult = require('./messages/result')
const ModifyResponse = require('./messages/modify_response')
const ModifyDNResponse = require('./messages/moddn_response')
const SearchRequest = require('./messages/search_request')
const LdapResult = messages.LdapResult
const AbandonResponse = messages.AbandonResponse
const AddResponse = messages.AddResponse
const BindResponse = messages.BindResponse
const CompareResponse = messages.CompareResponse
const DeleteResponse = messages.DeleteResponse
const ExtendedResponse = messages.ExtensionResponse
const ModifyResponse = messages.ModifyResponse
const ModifyDnResponse = messages.ModifyDnResponse
const SearchRequest = messages.SearchRequest
const SearchResponse = require('./messages/search_response')
const UnbindResponse = require('./messages/unbind_response')
/// --- Globals
// var Ber = asn1.Ber
// var BerReader = asn1.BerReader
const DN = dn.DN
// const DN = dn.DN
// var sprintf = util.format
@ -93,13 +94,20 @@ function getResponse (req) {
Response = ModifyResponse
break
case Protocol.operations.LDAP_REQ_MODRDN:
Response = ModifyDNResponse
Response = ModifyDnResponse
break
case Protocol.operations.LDAP_REQ_SEARCH:
Response = SearchResponse
break
case Protocol.operations.LDAP_REQ_UNBIND:
Response = UnbindResponse
// TODO: when the server receives an unbind request this made up response object was returned.
// Instead, we need to just terminate the connection. ~ jsumners
Response = class extends LdapResult {
status = 0
end () {
req.connection.end()
}
}
break
default:
return null
@ -107,16 +115,83 @@ function getResponse (req) {
assert.ok(Response)
const res = new Response({
messageID: req.messageID,
log: req.log,
messageId: req.messageId,
attributes: ((req instanceof SearchRequest) ? req.attributes : undefined)
})
res.log = req.log
res.connection = req.connection
res.logId = req.logId
if (typeof res.end !== 'function') {
// This is a hack to re-add the original tight coupling of the message
// objects and the server connection.
// TODO: remove this during server refactoring ~ jsumners 2023-02-16
switch (res.protocolOp) {
case 0: {
res.end = abandonResponseEnd
break
}
case Protocol.operations.LDAP_RES_COMPARE: {
res.end = compareResponseEnd
break
}
default: {
res.end = defaultResponseEnd
break
}
}
}
return res
}
/**
* Response connection end handler for most responses.
*
* @param {number} status
*/
function defaultResponseEnd (status) {
if (typeof status === 'number') { this.status = status }
const ber = this.toBer()
this.log.debug('%s: sending: %j', this.connection.ldap.id, this.pojo)
try {
this.connection.write(ber.buffer)
} catch (error) {
this.log.warn(
error,
'%s failure to write message %j',
this.connection.ldap.id,
this.pojo
)
}
}
/**
* Response connection end handler for ABANDON responses.
*/
function abandonResponseEnd () {}
/**
* Response connection end handler for COMPARE responses.
*
* @param {number | boolean} status
*/
function compareResponseEnd (status) {
let result = 0x06
if (typeof status === 'boolean') {
if (status === false) {
result = 0x05
}
} else {
result = status
}
return defaultResponseEnd.call(this, result)
}
function defaultHandler (req, res, next) {
assert.ok(req)
assert.ok(res)
@ -157,69 +232,6 @@ function noExOpHandler (req, res, next) {
return next()
}
function fireDTraceProbe (req, res) {
assert.ok(req)
req._dtraceId = res._dtraceId = dtrace._nextId()
const probeArgs = [
req._dtraceId,
req.connection.remoteAddress || 'localhost',
req.connection.ldap.bindDN.toString(),
req.dn.toString()
]
let op
switch (req.protocolOp) {
case Protocol.operations.LDAP_REQ_ABANDON:
op = 'abandon'
break
case Protocol.operations.LDAP_REQ_ADD:
op = 'add'
probeArgs.push(req.attributes.length)
break
case Protocol.operations.LDAP_REQ_BIND:
op = 'bind'
break
case Protocol.operations.LDAP_REQ_COMPARE:
op = 'compare'
probeArgs.push(req.attribute)
probeArgs.push(req.value)
break
case Protocol.operations.LDAP_REQ_DELETE:
op = 'delete'
break
case Protocol.operations.LDAP_REQ_EXTENSION:
op = 'exop'
probeArgs.push(req.name)
probeArgs.push(req.value)
break
case Protocol.operations.LDAP_REQ_MODIFY:
op = 'modify'
probeArgs.push(req.changes.length)
break
case Protocol.operations.LDAP_REQ_MODRDN:
op = 'modifydn'
probeArgs.push(req.newRdn.toString())
probeArgs.push((req.newSuperior ? req.newSuperior.toString() : ''))
break
case Protocol.operations.LDAP_REQ_SEARCH:
op = 'search'
probeArgs.push(req.scope)
probeArgs.push(req.filter.toString())
break
case Protocol.operations.LDAP_REQ_UNBIND:
op = 'unbind'
break
default:
break
}
res._dtraceOp = op
dtrace.fire('server-' + op + '-start', function () {
return probeArgs
})
}
/// --- API
/**
@ -261,8 +273,6 @@ function Server (options) {
this._chain = []
this.log = options.log
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true
const log = this.log
function setupConnection (c) {
@ -277,12 +287,12 @@ function Server (options) {
c.remotePort = c.socket.remotePort
}
const rdn = new dn.RDN({ cn: 'anonymous' })
const rdn = new RDN({ cn: 'anonymous' })
c.ldap = {
id: c.remoteAddress + ':' + c.remotePort,
config: options,
_bindDN: new DN([rdn])
_bindDN: new DN({ rdns: [rdn] })
}
c.addListener('timeout', function () {
log.trace('%s timed out', c.ldap.id)
@ -305,7 +315,9 @@ function Server (options) {
return c.ldap._bindDN
})
c.ldap.__defineSetter__('bindDN', function (val) {
if (!(val instanceof DN)) { throw new TypeError('DN required') }
if (Object.prototype.toString.call(val) !== '[object LdapDn]') {
throw new TypeError('DN required')
}
c.ldap._bindDN = val
return val
@ -327,11 +339,13 @@ function Server (options) {
log: options.log
})
conn.parser.on('message', function (req) {
// TODO: this is mutating the `@ldapjs/message` objects.
// We should avoid doing that. ~ jsumners 2023-02-16
req.connection = conn
req.logId = conn.ldap.id + '::' + req.messageID
req.logId = conn.ldap.id + '::' + req.messageId
req.startTime = new Date().getTime()
log.debug('%s: message received: req=%j', conn.ldap.id, req.json)
log.debug('%s: message received: req=%j', conn.ldap.id, req.pojo)
const res = getResponse(req)
if (!res) {
@ -343,31 +357,47 @@ function Server (options) {
// parse string DNs for routing/etc
try {
switch (req.protocolOp) {
case Protocol.operations.LDAP_REQ_BIND:
req.name = dn.parse(req.name)
case Protocol.operations.LDAP_REQ_BIND: {
req.name = DN.fromString(req.name)
break
}
case Protocol.operations.LDAP_REQ_ADD:
case Protocol.operations.LDAP_REQ_COMPARE:
case Protocol.operations.LDAP_REQ_DELETE:
req.entry = dn.parse(req.entry)
case Protocol.operations.LDAP_REQ_DELETE: {
if (typeof req.entry === 'string') {
req.entry = DN.fromString(req.entry)
} else if (Object.prototype.toString.call(req.entry) !== '[object LdapDn]') {
throw Error('invalid entry object for operation')
}
break
case Protocol.operations.LDAP_REQ_MODIFY:
req.object = dn.parse(req.object)
}
case Protocol.operations.LDAP_REQ_MODIFY: {
req.object = DN.fromString(req.object)
break
case Protocol.operations.LDAP_REQ_MODRDN:
req.entry = dn.parse(req.entry)
}
case Protocol.operations.LDAP_REQ_MODRDN: {
if (typeof req.entry === 'string') {
req.entry = DN.fromString(req.entry)
} else if (Object.prototype.toString.call(req.entry) !== '[object LdapDn]') {
throw Error('invalid entry object for operation')
}
// TODO: handle newRdn/Superior
break
case Protocol.operations.LDAP_REQ_SEARCH:
req.baseObject = dn.parse(req.baseObject)
}
case Protocol.operations.LDAP_REQ_SEARCH: {
break
default:
}
default: {
break
}
}
} catch (e) {
if (self.strictDN) {
return res.end(errors.LDAP_INVALID_DN_SYNTAX)
}
return res.end(errors.LDAP_INVALID_DN_SYNTAX)
}
res.connection = conn
@ -409,16 +439,16 @@ function Server (options) {
if (req.protocolOp === Protocol.operations.LDAP_REQ_BIND && res.status === 0) {
// 0 length == anonymous bind
if (req.dn.length === 0 && req.credentials === '') {
conn.ldap.bindDN = new DN([new dn.RDN({ cn: 'anonymous' })])
conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
} else {
conn.ldap.bindDN = req.dn
conn.ldap.bindDN = DN.fromString(req.dn)
}
}
// unbind clear bindDN for safety
// conn should terminate on unbind (RFC4511 4.3)
if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND && res.status === 0) {
conn.ldap.bindDN = new DN([new dn.RDN({ cn: 'anonymous' })])
conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
}
return after()
@ -725,12 +755,10 @@ Server.prototype.getConnections = function (callback) {
}
Server.prototype._getRoute = function (_dn, backend) {
assert.ok(dn)
if (!backend) { backend = this }
let name
if (_dn instanceof dn.DN) {
if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
name = _dn.toString()
} else {
name = _dn
@ -757,10 +785,10 @@ Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
Object.keys(this.routes).forEach(function (key) {
const _dn = self.routes[key].dn
// Ignore non-DN routes such as exop or unbind
if (_dn instanceof dn.DN) {
if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
const reversed = _dn.clone()
reversed.rdns.reverse()
reversedRDNsToKeys[reversed.format()] = key
reversed.reverse()
reversedRDNsToKeys[reversed.toString()] = key
}
})
const output = []
@ -777,11 +805,9 @@ Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
return this._routeKeyCache
}
Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
Server.prototype._getHandlerChain = function _getHandlerChain (req) {
assert.ok(req)
fireDTraceProbe(req, res)
const self = this
const routes = this.routes
let route
@ -837,7 +863,7 @@ Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
const keys = this._sortedRouteKeys()
let fallbackHandler = [noSuffixHandler]
// invalid DNs in non-strict mode are routed to the default handler
const testDN = (typeof (req.dn) === 'string') ? '' : req.dn
const testDN = (typeof (req.dn) === 'string') ? DN.fromString(req.dn) : req.dn
for (let i = 0; i < keys.length; i++) {
const suffix = keys[i]
@ -884,7 +910,7 @@ Server.prototype._mount = function (op, name, argv, notDN) {
backend = argv[0]
index = 1
}
const route = this._getRoute(notDN ? name : dn.parse(name), backend)
const route = this._getRoute(notDN ? name : DN.fromString(name), backend)
const chain = this._chain.slice()
argv.slice(index).forEach(function (a) {

View File

@ -2,7 +2,7 @@
const querystring = require('querystring')
const url = require('url')
const dn = require('./dn')
const { DN } = require('@ldapjs/dn')
const filter = require('@ldapjs/filter')
module.exports = {
@ -38,7 +38,7 @@ module.exports = {
if (u.pathname) {
u.pathname = querystring.unescape(u.pathname.substr(1))
u.DN = parseDN ? dn.parse(u.pathname) : u.pathname
u.DN = parseDN ? DN.fromString(u.pathname) : u.pathname
}
if (u.search) {

View File

@ -10,16 +10,14 @@
"url": "git://github.com/ldapjs/node-ldapjs.git"
},
"main": "lib/index.js",
"directories": {
"lib": "./lib"
},
"engines": {
"node": ">=10.13.0"
},
"dependencies": {
"@ldapjs/asn1": "1.2.0",
"@ldapjs/controls": "^1.0.0",
"@ldapjs/filter": "1.0.0",
"@ldapjs/asn1": "2.0.0-rc.4",
"@ldapjs/attribute": "1.0.0-rc.5",
"@ldapjs/change": "1.0.0-rc.3",
"@ldapjs/controls": "2.0.0-rc.1",
"@ldapjs/dn": "1.0.0-rc.1",
"@ldapjs/filter": "2.0.0-rc.5",
"@ldapjs/messages": "1.0.0-rc.2",
"@ldapjs/protocol": "^1.0.0",
"abstract-logging": "^2.0.0",
"assert-plus": "^1.0.0",

View File

@ -1,4 +1,8 @@
module.exports = {
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'no-shadow': 'off'
}

View File

@ -1,163 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { Attribute } = require('../lib')
test('new no args', function (t) {
t.ok(new Attribute())
t.end()
})
test('new with args', function (t) {
let attr = new Attribute({
type: 'cn',
vals: ['foo', 'bar']
})
t.ok(attr)
attr.addValue('baz')
t.equal(attr.type, 'cn')
t.equal(attr.vals.length, 3)
t.equal(attr.vals[0], 'foo')
t.equal(attr.vals[1], 'bar')
t.equal(attr.vals[2], 'baz')
t.throws(function () {
attr = new Attribute('not an object')
})
t.throws(function () {
const typeThatIsNotAString = 1
attr = new Attribute({
type: typeThatIsNotAString
})
})
t.end()
})
test('toBer', function (t) {
const attr = new Attribute({
type: 'cn',
vals: ['foo', 'bar']
})
t.ok(attr)
const ber = new BerWriter()
attr.toBer(ber)
const reader = new BerReader(ber.buffer)
t.ok(reader.readSequence())
t.equal(reader.readString(), 'cn')
t.equal(reader.readSequence(), 0x31) // lber set
t.equal(reader.readString(), 'foo')
t.equal(reader.readString(), 'bar')
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.startSequence()
ber.writeString('cn')
ber.startSequence(0x31)
ber.writeStringArray(['foo', 'bar'])
ber.endSequence()
ber.endSequence()
const attr = new Attribute()
t.ok(attr)
t.ok(attr.parse(new BerReader(ber.buffer)))
t.equal(attr.type, 'cn')
t.equal(attr.vals.length, 2)
t.equal(attr.vals[0], 'foo')
t.equal(attr.vals[1], 'bar')
t.end()
})
test('parse - without 0x31', function (t) {
const ber = new BerWriter()
ber.startSequence()
ber.writeString('sn')
ber.endSequence()
const attr = new Attribute()
t.ok(attr)
t.ok(attr.parse(new BerReader(ber.buffer)))
t.equal(attr.type, 'sn')
t.equal(attr.vals.length, 0)
t.end()
})
test('toString', function (t) {
const attr = new Attribute({
type: 'foobar',
vals: ['asdf']
})
const expected = attr.toString()
const actual = JSON.stringify(attr.json)
t.equal(actual, expected)
t.end()
})
test('isAttribute', function (t) {
const isA = Attribute.isAttribute
t.notOk(isA(null))
t.notOk(isA('asdf'))
t.ok(isA(new Attribute({
type: 'foobar',
vals: ['asdf']
})))
t.ok(isA({
type: 'foo',
vals: ['item', Buffer.alloc(5)],
toBer: function () { /* placeholder */ }
}))
// bad type in vals
t.notOk(isA({
type: 'foo',
vals: ['item', null],
toBer: function () { /* placeholder */ }
}))
t.end()
})
test('compare', function (t) {
const comp = Attribute.compare
const a = new Attribute({
type: 'foo',
vals: ['bar']
})
const b = new Attribute({
type: 'foo',
vals: ['bar']
})
const notAnAttribute = 'this is not an attribute'
t.throws(function () {
comp(a, notAnAttribute)
})
t.throws(function () {
comp(notAnAttribute, b)
})
t.equal(comp(a, b), 0)
// Different types
a.type = 'boo'
t.equal(comp(a, b), -1)
t.equal(comp(b, a), 1)
a.type = 'foo'
// Different value counts
a.vals = ['bar', 'baz']
t.equal(comp(a, b), 1)
t.equal(comp(b, a), -1)
// Different value contents (same count)
a.vals = ['baz']
t.equal(comp(a, b), 1)
t.equal(comp(b, a), -1)
t.end()
})

View File

@ -1,253 +0,0 @@
'use strict'
const fs = require('fs')
const path = require('path')
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { Attribute, Change } = require('../lib')
test('new no args', function (t) {
t.ok(new Change())
t.end()
})
test('new with args', function (t) {
const change = new Change({
operation: 'add',
modification: new Attribute({
type: 'cn',
vals: ['foo', 'bar']
})
})
t.ok(change)
t.equal(change.operation, 'add')
t.equal(change.modification.type, 'cn')
t.equal(change.modification.vals.length, 2)
t.equal(change.modification.vals[0], 'foo')
t.equal(change.modification.vals[1], 'bar')
t.end()
})
test('new with args and buffer', function (t) {
const img = fs.readFileSync(path.join(__dirname, '/imgs/test.jpg'))
const change = new Change({
operation: 'add',
modification: {
thumbnailPhoto: img
}
})
t.ok(change)
t.equal(change.operation, 'add')
t.equal(change.modification.type, 'thumbnailPhoto')
t.equal(change.modification.vals.length, 1)
t.equal(change.modification.buffers[0].compare(img), 0)
t.end()
})
test('validate fields', function (t) {
const c = new Change()
t.ok(c)
t.throws(function () {
c.operation = 'bogus'
})
t.throws(function () {
c.modification = { too: 'many', fields: 'here' }
})
c.modification = {
foo: ['bar', 'baz']
}
t.ok(c.modification)
t.end()
})
test('GH-31 (multiple attributes per Change)', function (t) {
t.throws(function () {
const c = new Change({
operation: 'replace',
modification: {
cn: 'foo',
sn: 'bar'
}
})
t.notOk(c)
})
t.end()
})
test('toBer', function (t) {
const change = new Change({
operation: 'Add',
modification: new Attribute({
type: 'cn',
vals: ['foo', 'bar']
})
})
t.ok(change)
const ber = new BerWriter()
change.toBer(ber)
const reader = new BerReader(ber.buffer)
t.ok(reader.readSequence())
t.equal(reader.readEnumeration(), 0x00)
t.ok(reader.readSequence())
t.equal(reader.readString(), 'cn')
t.equal(reader.readSequence(), 0x31) // lber set
t.equal(reader.readString(), 'foo')
t.equal(reader.readString(), 'bar')
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.startSequence()
ber.writeEnumeration(0x00)
ber.startSequence()
ber.writeString('cn')
ber.startSequence(0x31)
ber.writeStringArray(['foo', 'bar'])
ber.endSequence()
ber.endSequence()
ber.endSequence()
const change = new Change()
t.ok(change)
t.ok(change.parse(new BerReader(ber.buffer)))
t.equal(change.operation, 'add')
t.equal(change.modification.type, 'cn')
t.equal(change.modification.vals.length, 2)
t.equal(change.modification.vals[0], 'foo')
t.equal(change.modification.vals[1], 'bar')
t.end()
})
test('apply - replace', function (t) {
let res
const single = new Change({
operation: 'replace',
modification: {
type: 'cn',
vals: ['new']
}
})
const twin = new Change({
operation: 'replace',
modification: {
type: 'cn',
vals: ['new', 'two']
}
})
const empty = new Change({
operation: 'replace',
modification: {
type: 'cn',
vals: []
}
})
// plain
res = Change.apply(single, { cn: ['old'] })
t.same(res.cn, ['new'])
// multiple
res = Change.apply(single, { cn: ['old', 'also'] })
t.same(res.cn, ['new'])
// empty
res = Change.apply(empty, { cn: ['existing'] })
t.equal(res.cn, undefined)
t.ok(Object.keys(res).indexOf('cn') === -1)
// absent
res = Change.apply(single, { dn: ['otherjunk'] })
t.same(res.cn, ['new'])
// scalar formatting "success"
res = Change.apply(single, { cn: 'old' }, true)
t.equal(res.cn, 'new')
// scalar formatting "failure"
res = Change.apply(twin, { cn: 'old' }, true)
t.same(res.cn, ['new', 'two'])
t.end()
})
test('apply - add', function (t) {
let res
const single = new Change({
operation: 'add',
modification: {
type: 'cn',
vals: ['new']
}
})
// plain
res = Change.apply(single, { cn: ['old'] })
t.same(res.cn, ['old', 'new'])
// multiple
res = Change.apply(single, { cn: ['old', 'also'] })
t.same(res.cn, ['old', 'also', 'new'])
// absent
res = Change.apply(single, { dn: ['otherjunk'] })
t.same(res.cn, ['new'])
// scalar formatting "success"
res = Change.apply(single, { }, true)
t.equal(res.cn, 'new')
// scalar formatting "failure"
res = Change.apply(single, { cn: 'old' }, true)
t.same(res.cn, ['old', 'new'])
// duplicate add
res = Change.apply(single, { cn: 'new' })
t.same(res.cn, ['new'])
t.end()
})
test('apply - delete', function (t) {
let res
const single = new Change({
operation: 'delete',
modification: {
type: 'cn',
vals: ['old']
}
})
// plain
res = Change.apply(single, { cn: ['old', 'new'] })
t.same(res.cn, ['new'])
// empty
res = Change.apply(single, { cn: ['old'] })
t.equal(res.cn, undefined)
t.ok(Object.keys(res).indexOf('cn') === -1)
// scalar formatting "success"
res = Change.apply(single, { cn: ['old', 'one'] }, true)
t.equal(res.cn, 'one')
// scalar formatting "failure"
res = Change.apply(single, { cn: ['old', 'several', 'items'] }, true)
t.same(res.cn, ['several', 'items'])
// absent
res = Change.apply(single, { dn: ['otherjunk'] })
t.ok(res)
t.equal(res.cn, undefined)
t.end()
})

View File

@ -6,8 +6,18 @@ const tap = require('tap')
const vasync = require('vasync')
const getPort = require('get-port')
const { getSock, uuid } = require('./utils')
const Attribute = require('@ldapjs/attribute')
const Change = require('@ldapjs/change')
const messages = require('@ldapjs/messages')
const controls = require('@ldapjs/controls')
const ldap = require('../lib')
const { Attribute, Change } = ldap
const {
SearchRequest,
SearchResultEntry,
SearchResultReference,
SearchResultDone
} = messages
const SUFFIX = 'dc=test'
const LDAP_CONNECT_TIMEOUT = process.env.LDAP_CONNECT_TIMEOUT || 0
@ -44,7 +54,7 @@ tap.beforeEach((t) => {
// LDAP whoami
server.exop('1.3.6.1.4.1.4203.1.11.3', function (req, res, next) {
res.value = 'u:xxyyz@EXAMPLE.NET'
res.responseValue = 'u:xxyyz@EXAMPLE.NET'
res.end()
return next()
})
@ -88,21 +98,21 @@ tap.beforeEach((t) => {
if (req.dn.equals('cn=ref,' + SUFFIX)) {
res.send(res.createSearchReference('ldap://localhost'))
} else if (req.dn.equals('cn=bin,' + SUFFIX)) {
const attributes = []
attributes.push(new Attribute({ type: 'foo;binary', values: ['wr0gKyDCvCA9IMK+'] }))
attributes.push(new Attribute({ type: 'gb18030', values: [Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA])] }))
attributes.push(new Attribute({ type: 'objectclass', values: ['binary'] }))
res.send(res.createSearchEntry({
objectName: req.dn,
attributes: {
'foo;binary': 'wr0gKyDCvCA9IMK+',
gb18030: Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA]),
objectclass: 'binary'
}
attributes
}))
} else {
const attributes = []
attributes.push(new Attribute({ type: 'cn', values: ['unit', 'test'] }))
attributes.push(new Attribute({ type: 'SN', values: ['testy'] }))
const e = res.createSearchEntry({
objectName: req.dn,
attributes: {
cn: ['unit', 'test'],
SN: 'testy'
}
attributes
})
res.send(e)
res.send(e)
@ -142,13 +152,14 @@ tap.beforeEach((t) => {
end = (end > max || end < min) ? max : end
let i
for (i = start; i < end; i++) {
res.send({
dn: util.format('o=%d, cn=paged', i),
attributes: {
res.send(new SearchResultEntry({
messageId: res.id,
entry: `o=${i},cn=paged`,
attributes: Attribute.fromObject({
o: [i],
objectclass: ['pagedResult']
}
})
})
}))
}
return i
}
@ -156,39 +167,40 @@ tap.beforeEach((t) => {
let cookie = null
let pageSize = 0
req.controls.forEach(function (control) {
if (control.type === ldap.PagedResultsControl.OID) {
if (control.type === controls.PagedResultsControl.OID) {
pageSize = control.value.size
cookie = control.value.cookie
}
})
if (cookie && Buffer.isBuffer(cookie)) {
// Do simple paging
let first = min
if (cookie.length !== 0) {
first = parseInt(cookie.toString(), 10)
}
const last = sendResults(first, first + pageSize)
let resultCookie
if (last < max) {
resultCookie = Buffer.from(last.toString())
} else {
resultCookie = Buffer.from('')
}
res.controls.push(new ldap.PagedResultsControl({
value: {
size: pageSize, // correctness not required here
cookie: resultCookie
}
}))
res.end()
next()
} else {
if (!cookie || Buffer.isBuffer(cookie) === false) {
// don't allow non-paged searches for this test endpoint
next(new ldap.UnwillingToPerformError())
next(Error('unwilling to perform'))
}
// Do simple paging
let first = min
if (cookie.length !== 0) {
first = parseInt(cookie.toString(), 10)
}
const last = sendResults(first, first + pageSize)
let resultCookie
if (last < max) {
resultCookie = Buffer.from(last.toString())
} else {
resultCookie = Buffer.from('')
}
res.addControl(new controls.PagedResultsControl({
value: {
size: pageSize, // correctness not required here
cookie: resultCookie
}
}))
res.end()
next()
})
server.search('cn=sssvlv', function (req, res, next) {
const min = 0
const max = 100
@ -484,7 +496,7 @@ tap.test('add success', function (t) {
const attrs = [
new Attribute({
type: 'cn',
vals: ['test']
values: ['test']
})
]
t.context.client.add('cn=add, ' + SUFFIX, attrs, function (err, res) {
@ -510,7 +522,7 @@ tap.test('add success with object', function (t) {
tap.test('add buffer', function (t) {
const { BerReader } = require('@ldapjs/asn1')
const dn = `cn=add, ${SUFFIX}`
const dn = `cn=add,${SUFFIX}`
const attribute = 'thumbnailPhoto'
const binary = 0xa5
const entry = {
@ -519,7 +531,7 @@ tap.test('add buffer', function (t) {
const write = t.context.client._socket.write
t.context.client._socket.write = (data, encoding, cb) => {
const reader = new BerReader(data)
t.equal(data.byteLength, 49)
t.equal(data.byteLength, 48)
t.ok(reader.readSequence())
t.equal(reader.readInt(), 0x1)
t.equal(reader.readSequence(), 0x68)
@ -621,7 +633,7 @@ tap.test('modify success', function (t) {
type: 'Replace',
modification: new Attribute({
type: 'cn',
vals: ['test']
values: ['test']
})
})
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
@ -632,26 +644,11 @@ tap.test('modify success', function (t) {
})
})
tap.test('modify change plain object success', function (t) {
const change = new Change({
type: 'Replace',
modification: {
cn: 'test'
}
})
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
t.error(err)
t.ok(res)
t.equal(res.status, 0)
t.end()
})
})
// https://github.com/ldapjs/node-ldapjs/pull/435
tap.test('can delete attributes', function (t) {
const change = new Change({
type: 'Delete',
modification: { cn: null }
modification: new Attribute({ type: 'cn', values: [null] })
})
t.context.client.modify('cn=modify,' + SUFFIX, change, function (err, res) {
t.error(err)
@ -667,7 +664,7 @@ tap.test('modify array success', function (t) {
operation: 'Replace',
modification: new Attribute({
type: 'cn',
vals: ['test']
values: ['test']
})
}),
new Change({
@ -685,22 +682,6 @@ tap.test('modify array success', function (t) {
})
})
tap.test('modify change plain object success (GH-31)', function (t) {
const change = {
type: 'replace',
modification: {
cn: 'test',
sn: 'bar'
}
}
t.context.client.modify('cn=modify, ' + SUFFIX, change, function (err, res) {
t.error(err)
t.ok(res)
t.equal(res.status, 0)
t.end()
})
})
tap.test('modify DN new RDN only', function (t) {
t.context.client.modifyDN('cn=old, ' + SUFFIX, 'cn=new', function (err, res) {
t.error(err)
@ -729,47 +710,42 @@ tap.test('modify DN excessive length (GH-480)', function (t) {
})
tap.test('modify DN excessive superior length', function (t) {
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const ModifyDNRequest = require('../lib/messages/moddn_request')
const ber = new BerWriter()
const { ModifyDnRequest } = messages
const entry = 'cn=Test User,ou=A Long OU ,ou=Another Long OU ,ou=Another Long OU ,dc=acompany,DC=io'
const newSuperior = 'ou=A New Long OU , ou=Another New Long OU , ou=An OU , dc=acompany, dc=io'
const newRdn = entry.replace(/(.*?),.*/, '$1')
const deleteOldRdn = true
const req = new ModifyDNRequest({
entry: entry,
deleteOldRdn: deleteOldRdn,
controls: []
const req = new ModifyDnRequest({
entry,
deleteOldRdn,
newRdn,
newSuperior
})
req.newRdn = newRdn
req.newSuperior = newSuperior
req._toBer(ber)
const reader = new BerReader(ber.buffer)
t.equal(reader.readString(), entry)
t.equal(reader.readString(), newRdn)
t.equal(reader.readBoolean(), deleteOldRdn)
t.equal(reader.readByte(), 0x80)
reader.readLength()
t.equal(reader._len, newSuperior.length)
reader._buf[--reader._offset] = 0x4
t.equal(reader.readString(), newSuperior)
t.equal(req.entry.toString(), 'cn=Test User,ou=A Long OU,ou=Another Long OU,ou=Another Long OU,dc=acompany,DC=io')
t.equal(req.newRdn.toString(), 'cn=Test User')
t.equal(req.deleteOldRdn, true)
t.equal(req.newSuperior.toString(), 'ou=A New Long OU,ou=Another New Long OU,ou=An OU,dc=acompany,dc=io')
t.end()
})
tap.test('search basic', function (t) {
const { SearchResultEntry, SearchResultDone } = messages
t.context.client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) {
t.error(err)
t.ok(res)
let gotEntry = 0
res.on('searchEntry', function (entry) {
t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry)
t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
t.ok(entry.attributes)
t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'cn')
t.equal(entry.attributes[1].type, 'SN')
t.ok(entry.object)
gotEntry++
})
res.on('error', function (err) {
@ -777,7 +753,7 @@ tap.test('search basic', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 2)
t.end()
@ -844,7 +820,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
t2.error(err)
res.on('searchEntry', entryListener)
res.on('searchRequest', (searchRequest) => {
t2.ok(searchRequest instanceof ldap.SearchRequest)
t2.ok(searchRequest instanceof SearchRequest)
if (currentSearchRequest === null) {
t2.equal(countPages, 0)
}
@ -855,7 +831,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
res.on('end', function (result) {
t2.equal(countEntries, 1000)
t2.equal(countPages, 10)
t2.equal(result.messageID, currentSearchRequest.messageID)
t2.equal(result.messageId, currentSearchRequest.messageId)
t2.end()
})
@ -871,7 +847,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
function pageListener (result) {
countPages += 1
if (countPages < 10) {
t2.equal(result.messageID, currentSearchRequest.messageID)
t2.equal(result.messageId, currentSearchRequest.messageId)
}
}
})
@ -897,7 +873,7 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
countPages++
// cancel after 9 to verify callback usage
if (countPages === 9) {
// another page should never be encountered
// another page should never be encountered
res.removeListener('page', pageListener)
.on('page', t2.fail.bind(null, 'unexpected page'))
return cb(new Error())
@ -995,7 +971,12 @@ tap.test('search paged', { timeout: 10000 }, function (t) {
t.end()
})
tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
// We are skipping the ServerSideSorting test because we have skipped
// properly implementing the controls in order to get v3 shipped. These
// tests should be re-enabled once we have addressed this issue.
// ~ jsumners 2023-02-19
// TODO: re-enable after adding back SSSR support
tap.test('search - sssvlv', { timeout: 10000, skip: true }, function (t) {
t.test('ssv - asc', function (t2) {
let preventry = null
const sssrcontrol = new ldap.ServerSideSortingRequestControl(
@ -1025,6 +1006,7 @@ tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
})
})
})
t.test('ssv - desc', function (t2) {
let preventry = null
const sssrcontrol = new ldap.ServerSideSortingRequestControl(
@ -1099,6 +1081,7 @@ tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
})
})
})
t.test('vlv - last page', { skip: true }, function (t2) {
// This test is disabled.
// See https://github.com/ldapjs/node-ldapjs/pull/797#issuecomment-1094132289
@ -1143,6 +1126,7 @@ tap.test('search - sssvlv', { timeout: 10000 }, function (t) {
})
})
})
t.end()
})
@ -1158,7 +1142,7 @@ tap.test('search referral', function (t) {
res.on('searchReference', function (referral) {
gotReferral = true
t.ok(referral)
t.ok(referral instanceof ldap.SearchReference)
t.ok(referral instanceof SearchResultReference)
t.ok(referral.uris)
t.ok(referral.uris.length)
})
@ -1167,7 +1151,7 @@ tap.test('search referral', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 0)
t.ok(gotReferral)
@ -1184,14 +1168,13 @@ tap.test('search rootDSE', function (t) {
t.ok(entry)
t.equal(entry.dn.toString(), '')
t.ok(entry.attributes)
t.ok(entry.object)
})
res.on('error', function (err) {
t.fail(err)
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.end()
})
@ -1204,12 +1187,16 @@ tap.test('search empty attribute', function (t) {
t.ok(res)
let gotEntry = 0
res.on('searchEntry', function (entry) {
const obj = entry.toObject()
t.equal('dc=empty', obj.dn)
t.ok(obj.member)
t.equal(obj.member.length, 0)
t.ok(obj['member;range=0-1'])
t.ok(obj['member;range=0-1'].length)
const obj = entry.pojo
t.equal('dc=empty', obj.objectName)
const member = entry.attributes[0]
t.ok(member)
t.equal(member.values.length, 0)
const rangedMember = entry.attributes[1]
t.equal(rangedMember.type, 'member;range=0-1')
t.equal(rangedMember.values.length, 2)
gotEntry++
})
res.on('error', function (err) {
@ -1217,7 +1204,7 @@ tap.test('search empty attribute', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 1)
t.end()
@ -1234,12 +1221,12 @@ tap.test('GH-21 binary attributes', function (t) {
const expect2 = Buffer.from([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA])
res.on('searchEntry', function (entry) {
t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry)
t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=bin,' + SUFFIX)
t.ok(entry.attributes)
t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'foo;binary')
t.equal(entry.attributes[0].vals[0], expect.toString('base64'))
t.equal(entry.attributes[0].values[0], expect.toString('base64'))
t.equal(entry.attributes[0].buffers[0].toString('base64'),
expect.toString('base64'))
@ -1248,7 +1235,6 @@ tap.test('GH-21 binary attributes', function (t) {
t.equal(expect2.length, entry.attributes[1].buffers[0].length)
for (let i = 0; i < expect2.length; i++) { t.equal(expect2[i], entry.attributes[1].buffers[0][i]) }
t.ok(entry.object)
gotEntry++
})
res.on('error', function (err) {
@ -1256,7 +1242,7 @@ tap.test('GH-21 binary attributes', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 1)
t.end()
@ -1267,7 +1253,7 @@ tap.test('GH-21 binary attributes', function (t) {
tap.test('GH-23 case insensitive attribute filtering', function (t) {
const opts = {
filter: '(objectclass=*)',
attributes: ['Cn']
attributes: ['@Cn']
}
t.context.client.search('cn=test, ' + SUFFIX, opts, function (err, res) {
t.error(err)
@ -1275,12 +1261,11 @@ tap.test('GH-23 case insensitive attribute filtering', function (t) {
let gotEntry = 0
res.on('searchEntry', function (entry) {
t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry)
t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
t.ok(entry.attributes)
t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'cn')
t.ok(entry.object)
gotEntry++
})
res.on('error', function (err) {
@ -1288,7 +1273,7 @@ tap.test('GH-23 case insensitive attribute filtering', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 2)
t.end()
@ -1307,13 +1292,12 @@ tap.test('GH-24 attribute selection of *', function (t) {
let gotEntry = 0
res.on('searchEntry', function (entry) {
t.ok(entry)
t.ok(entry instanceof ldap.SearchEntry)
t.ok(entry instanceof SearchResultEntry)
t.equal(entry.dn.toString(), 'cn=test,' + SUFFIX)
t.ok(entry.attributes)
t.ok(entry.attributes.length)
t.equal(entry.attributes[0].type, 'cn')
t.equal(entry.attributes[1].type, 'SN')
t.ok(entry.object)
gotEntry++
})
res.on('error', function (err) {
@ -1321,7 +1305,7 @@ tap.test('GH-24 attribute selection of *', function (t) {
})
res.on('end', function (res) {
t.ok(res)
t.ok(res instanceof ldap.SearchResponse)
t.ok(res instanceof SearchResultDone)
t.equal(res.status, 0)
t.equal(gotEntry, 2)
t.end()
@ -1658,7 +1642,7 @@ tap.test('connection timeout', function (t) {
})
})
tap.only('emitError', function (t) {
tap.test('emitError', function (t) {
t.test('connectTimeout', function (t) {
getPort().then(function (unusedPortNumber) {
const client = ldap.createClient({

View File

@ -1,234 +0,0 @@
'use strict'
const { test } = require('tap')
const { dn } = require('../lib')
test('parse basic', function (t) {
const DN_STR = 'cn=mark, ou=people, o=joyent'
const name = dn.parse(DN_STR)
t.ok(name)
t.ok(name.rdns)
t.ok(Array.isArray(name.rdns))
t.equal(3, name.rdns.length)
name.rdns.forEach(function (rdn) {
t.equal('object', typeof (rdn))
})
t.equal(name.toString(), DN_STR)
t.end()
})
test('parse escaped', function (t) {
const DN_STR = 'cn=m\\,ark, ou=people, o=joyent'
const name = dn.parse(DN_STR)
t.ok(name)
t.ok(name.rdns)
t.ok(Array.isArray(name.rdns))
t.equal(3, name.rdns.length)
name.rdns.forEach(function (rdn) {
t.equal('object', typeof (rdn))
})
t.equal(name.toString(), DN_STR)
t.end()
})
test('parse compound', function (t) {
const DN_STR = 'cn=mark+sn=cavage, ou=people, o=joyent'
const name = dn.parse(DN_STR)
t.ok(name)
t.ok(name.rdns)
t.ok(Array.isArray(name.rdns))
t.equal(3, name.rdns.length)
name.rdns.forEach(function (rdn) {
t.equal('object', typeof (rdn))
})
t.equal(name.toString(), DN_STR)
t.end()
})
test('parse quoted', function (t) {
const DN_STR = 'cn="mark+sn=cavage", ou=people, o=joyent'
const ESCAPE_STR = 'cn=mark\\+sn\\=cavage, ou=people, o=joyent'
const name = dn.parse(DN_STR)
t.ok(name)
t.ok(name.rdns)
t.ok(Array.isArray(name.rdns))
t.equal(3, name.rdns.length)
name.rdns.forEach(function (rdn) {
t.equal('object', typeof (rdn))
})
t.equal(name.toString(), ESCAPE_STR)
t.end()
})
test('equals', function (t) {
const dn1 = dn.parse('cn=foo,dc=bar')
t.ok(dn1.equals('cn=foo,dc=bar'))
t.ok(!dn1.equals('cn=foo1,dc=bar'))
t.ok(dn1.equals(dn.parse('cn=foo,dc=bar')))
t.ok(!dn1.equals(dn.parse('cn=foo2,dc=bar')))
t.end()
})
test('child of', function (t) {
const dn1 = dn.parse('cn=foo,dc=bar')
t.ok(dn1.childOf('dc=bar'))
t.ok(!dn1.childOf('dc=moo'))
t.ok(!dn1.childOf('dc=foo'))
t.ok(!dn1.childOf('cn=foo,dc=bar'))
t.ok(dn1.childOf(dn.parse('dc=bar')))
t.end()
})
test('parent of', function (t) {
const dn1 = dn.parse('cn=foo,dc=bar')
t.ok(dn1.parentOf('cn=moo,cn=foo,dc=bar'))
t.ok(!dn1.parentOf('cn=moo,cn=bar,dc=foo'))
t.ok(!dn1.parentOf('cn=foo,dc=bar'))
t.ok(dn1.parentOf(dn.parse('cn=moo,cn=foo,dc=bar')))
t.end()
})
test('DN parent', function (t) {
const _dn = dn.parse('cn=foo,ou=bar')
const parent1 = _dn.parent()
const parent2 = parent1.parent()
t.ok(parent1.equals('ou=bar'))
t.ok(parent2.equals(''))
t.equal(parent2.parent(), null)
t.end()
})
test('empty DNs', function (t) {
const _dn = dn.parse('')
const _dn2 = dn.parse('cn=foo')
t.ok(_dn.isEmpty())
t.notOk(_dn2.isEmpty())
t.notOk(_dn.equals('cn=foo'))
t.notOk(_dn2.equals(''))
t.ok(_dn.parentOf('cn=foo'))
t.notOk(_dn.childOf('cn=foo'))
t.notOk(_dn2.parentOf(''))
t.ok(_dn2.childOf(''))
t.end()
})
test('case insensitive attribute names', function (t) {
const dn1 = dn.parse('CN=foo,dc=bar')
t.ok(dn1.equals('cn=foo,dc=bar'))
t.ok(dn1.equals(dn.parse('cn=foo,DC=bar')))
t.end()
})
test('format', function (t) {
const DN_ORDER = dn.parse('sn=bar+cn=foo,ou=test')
const DN_QUOTE = dn.parse('cn="foo",ou=test')
const DN_QUOTE2 = dn.parse('cn=" foo",ou=test')
const DN_SPACE = dn.parse('cn=foo,ou=test')
const DN_SPACE2 = dn.parse('cn=foo ,ou=test')
const DN_CASE = dn.parse('CN=foo,Ou=test')
t.equal(DN_ORDER.format({ keepOrder: false }), 'cn=foo+sn=bar, ou=test')
t.equal(DN_ORDER.format({ keepOrder: true }), 'sn=bar+cn=foo, ou=test')
t.equal(DN_QUOTE.format({ keepQuote: false }), 'cn=foo, ou=test')
t.equal(DN_QUOTE.format({ keepQuote: true }), 'cn="foo", ou=test')
t.equal(DN_QUOTE2.format({ keepQuote: false }), 'cn=" foo", ou=test')
t.equal(DN_QUOTE2.format({ keepQuote: true }), 'cn=" foo", ou=test')
t.equal(DN_SPACE.format({ keepSpace: false }), 'cn=foo, ou=test')
t.equal(DN_SPACE.format({ keepSpace: true }), 'cn=foo,ou=test')
t.equal(DN_SPACE.format({ skipSpace: true }), 'cn=foo,ou=test')
t.equal(DN_SPACE2.format({ keepSpace: false }), 'cn=foo, ou=test')
t.equal(DN_SPACE2.format({ keepSpace: true }), 'cn=foo ,ou=test')
t.equal(DN_SPACE2.format({ skipSpace: true }), 'cn=foo,ou=test')
t.equal(DN_CASE.format({ keepCase: false }), 'cn=foo, ou=test')
t.equal(DN_CASE.format({ keepCase: true }), 'CN=foo, Ou=test')
t.equal(DN_CASE.format({ upperName: true }), 'CN=foo, OU=test')
t.end()
})
test('set format', function (t) {
const _dn = dn.parse('uid="user", sn=bar+cn=foo, dc=test , DC=com')
t.equal(_dn.toString(), 'uid=user, cn=foo+sn=bar, dc=test, dc=com')
_dn.setFormat({ keepOrder: true })
t.equal(_dn.toString(), 'uid=user, sn=bar+cn=foo, dc=test, dc=com')
_dn.setFormat({ keepQuote: true })
t.equal(_dn.toString(), 'uid="user", cn=foo+sn=bar, dc=test, dc=com')
_dn.setFormat({ keepSpace: true })
t.equal(_dn.toString(), 'uid=user, cn=foo+sn=bar, dc=test , dc=com')
_dn.setFormat({ keepCase: true })
t.equal(_dn.toString(), 'uid=user, cn=foo+sn=bar, dc=test, DC=com')
_dn.setFormat({ upperName: true })
t.equal(_dn.toString(), 'UID=user, CN=foo+SN=bar, DC=test, DC=com')
t.end()
})
test('format persists across clone', function (t) {
const _dn = dn.parse('uid="user", sn=bar+cn=foo, dc=test , DC=com')
const OUT = 'UID="user", CN=foo+SN=bar, DC=test, DC=com'
_dn.setFormat({ keepQuote: true, upperName: true })
const clone = _dn.clone()
t.equal(_dn.toString(), OUT)
t.equal(clone.toString(), OUT)
t.end()
})
test('initialization', function (t) {
const dn1 = new dn.DN()
t.ok(dn1)
t.equal(dn1.toString(), '')
t.ok(dn1.isEmpty(), 'DN with no initializer defaults to null DN')
const data = [
new dn.RDN({ foo: 'bar' }),
new dn.RDN({ o: 'base' })
]
const dn2 = new dn.DN(data)
t.ok(dn2)
t.equal(dn2.toString(), 'foo=bar, o=base')
t.ok(!dn2.isEmpty())
t.end()
})
test('array functions', function (t) {
const dn1 = dn.parse('a=foo, b=bar, c=baz')
t.ok(dn1)
t.equal(dn1.toString(), 'a=foo, b=bar, c=baz')
t.ok(dn1.reverse())
t.equal(dn1.toString(), 'c=baz, b=bar, a=foo')
let rdn = dn1.pop()
t.ok(rdn)
t.equal(dn1.toString(), 'c=baz, b=bar')
t.ok(dn1.push(rdn))
t.equal(dn1.toString(), 'c=baz, b=bar, a=foo')
rdn = dn1.shift()
t.ok(rdn)
t.equal(dn1.toString(), 'b=bar, a=foo')
t.ok(dn1.unshift(rdn))
t.equal(dn1.toString(), 'c=baz, b=bar, a=foo')
t.end()
})
test('isDN duck-testing', function (t) {
const valid = dn.parse('cn=foo')
const isDN = dn.DN.isDN
t.notOk(isDN(null))
t.notOk(isDN('cn=foo'))
t.ok(isDN(valid))
const duck = {
rdns: [{ look: 'ma' }, { a: 'dn' }],
toString: function () { return 'look=ma, a=dn' }
}
t.ok(isDN(duck))
t.end()
})

View File

@ -2,6 +2,8 @@
const tap = require('tap')
const { getSock, uuid } = require('./utils')
const { SearchResultEntry } = require('@ldapjs/messages')
const Attribute = require('@ldapjs/attribute')
const ldap = require('../lib')
function search (t, options, callback) {
@ -40,18 +42,20 @@ tap.beforeEach((t) => {
})
server.search(suffix, function (req, res) {
const entry = {
dn: 'cn=foo, ' + suffix,
attributes: {
const entry = new SearchResultEntry({
entry: 'cn=foo,' + suffix,
attributes: Attribute.fromObject({
objectclass: ['person', 'top'],
cn: 'Pogo Stick',
sn: 'Stick',
givenname: 'ogo',
mail: uuid() + '@pogostick.org'
}
}
})
})
if (req.filter.matches(entry.attributes)) { res.send(entry) }
if (req.filter.matches(entry.attributes)) {
res.send(entry)
}
res.end()
})
@ -87,7 +91,7 @@ tap.afterEach((t) => {
})
})
tap.test('Evolution search filter (GH-3)', { only: true }, function (t) {
tap.test('Evolution search filter (GH-3)', function (t) {
// This is what Evolution sends, when searching for a contact 'ogo'. Wow.
const filter =
'(|(cn=ogo*)(givenname=ogo*)(sn=ogo*)(mail=ogo*)(member=ogo*)' +
@ -114,7 +118,7 @@ tap.test('GH-49 Client errors on bad attributes', function (t) {
const searchOpts = {
filter: 'cn=*ogo*',
scope: 'one',
attributes: 'dn'
attributes: '@dn'
}
return search(t, searchOpts)
})

View File

@ -109,7 +109,7 @@ tap.test('#fetch', t => {
t.test('returns handler for fetched message', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
tracker.track({}, handler)
const fetched = tracker.fetch(1)
const { callback: fetched } = tracker.fetch(1)
t.equal(fetched, handler)
function handler () {}
@ -120,7 +120,7 @@ tap.test('#fetch', t => {
tracker.track({}, handler)
tracker.track({ abandon: 'message' }, () => {})
tracker.abandon(1)
const fetched = tracker.fetch(1)
const { callback: fetched } = tracker.fetch(1)
t.equal(fetched, handler)
function handler () {}
@ -185,13 +185,13 @@ tap.test('#remove', t => {
})
tap.test('#track', t => {
t.test('add messageID and tracks message', async t => {
t.test('add messageId and tracks message', async t => {
const tracker = messageTrackerFactory({ id: 'foo', parser: {} })
const msg = {}
tracker.track(msg, handler)
t.same(msg, { messageID: 1 })
const cb = tracker.fetch(1)
t.same(msg, { messageId: 1 })
const { callback: cb } = tracker.fetch(1)
t.equal(cb, handler)
function handler () {}

View File

@ -1,118 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { AddRequest, Attribute, dn } = require('../../lib')
test('new no args', t => {
t.ok(new AddRequest())
t.end()
})
test('new with args', t => {
const req = new AddRequest({
entry: dn.parse('cn=foo, o=test'),
attributes: [new Attribute({ type: 'cn', vals: ['foo'] }),
new Attribute({ type: 'objectclass', vals: ['person'] })]
})
t.ok(req)
t.equal(req.dn.toString(), 'cn=foo, o=test')
t.equal(req.attributes.length, 2)
t.equal(req.attributes[0].type, 'cn')
t.equal(req.attributes[0].vals[0], 'foo')
t.equal(req.attributes[1].type, 'objectclass')
t.equal(req.attributes[1].vals[0], 'person')
t.end()
})
test('parse', t => {
const ber = new BerWriter()
ber.writeString('cn=foo, o=test')
ber.startSequence()
ber.startSequence()
ber.writeString('cn')
ber.startSequence(0x31)
ber.writeString('foo')
ber.endSequence()
ber.endSequence()
ber.startSequence()
ber.writeString('objectclass')
ber.startSequence(0x31)
ber.writeString('person')
ber.endSequence()
ber.endSequence()
ber.endSequence()
const req = new AddRequest()
t.ok(req._parse(new BerReader(ber.buffer)))
t.equal(req.dn.toString(), 'cn=foo, o=test')
t.equal(req.attributes.length, 2)
t.equal(req.attributes[0].type, 'cn')
t.equal(req.attributes[0].vals[0], 'foo')
t.equal(req.attributes[1].type, 'objectclass')
t.equal(req.attributes[1].vals[0], 'person')
t.end()
})
test('toBer', t => {
const req = new AddRequest({
messageID: 123,
entry: dn.parse('cn=foo, o=test'),
attributes: [new Attribute({ type: 'cn', vals: ['foo'] }),
new Attribute({ type: 'objectclass', vals: ['person'] })]
})
t.ok(req)
const ber = new BerReader(req.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x68)
t.equal(ber.readString(), 'cn=foo, o=test')
t.ok(ber.readSequence())
t.ok(ber.readSequence())
t.equal(ber.readString(), 'cn')
t.equal(ber.readSequence(), 0x31)
t.equal(ber.readString(), 'foo')
t.ok(ber.readSequence())
t.equal(ber.readString(), 'objectclass')
t.equal(ber.readSequence(), 0x31)
t.equal(ber.readString(), 'person')
t.end()
})
test('toObject', t => {
const req = new AddRequest({
entry: dn.parse('cn=foo, o=test'),
attributes: [new Attribute({ type: 'cn', vals: ['foo', 'bar'] }),
new Attribute({ type: 'objectclass', vals: ['person'] })]
})
t.ok(req)
const obj = req.toObject()
t.ok(obj)
t.ok(obj.dn)
t.equal(obj.dn, 'cn=foo, o=test')
t.ok(obj.attributes)
t.ok(obj.attributes.cn)
t.ok(Array.isArray(obj.attributes.cn))
t.equal(obj.attributes.cn.length, 2)
t.equal(obj.attributes.cn[0], 'foo')
t.equal(obj.attributes.cn[1], 'bar')
t.ok(obj.attributes.objectclass)
t.ok(Array.isArray(obj.attributes.objectclass))
t.equal(obj.attributes.objectclass.length, 1)
t.equal(obj.attributes.objectclass[0], 'person')
t.end()
})

View File

@ -1,56 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { AddResponse } = require('../../lib')
test('new no args', function (t) {
t.ok(new AddResponse())
t.end()
})
test('new with args', function (t) {
const res = new AddResponse({
messageID: 123,
status: 0
})
t.ok(res)
t.equal(res.messageID, 123)
t.equal(res.status, 0)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeEnumeration(0)
ber.writeString('cn=root')
ber.writeString('foo')
const res = new AddResponse()
t.ok(res._parse(new BerReader(ber.buffer)))
t.equal(res.status, 0)
t.equal(res.matchedDN, 'cn=root')
t.equal(res.errorMessage, 'foo')
t.end()
})
test('toBer', function (t) {
const res = new AddResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
})
t.ok(res)
const ber = new BerReader(res.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x69)
t.equal(ber.readEnumeration(), 3)
t.equal(ber.readString(), 'cn=root')
t.equal(ber.readString(), 'foo')
t.end()
})

View File

@ -1,58 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { BindRequest, dn } = require('../../lib')
test('new no args', function (t) {
t.ok(new BindRequest())
t.end()
})
test('new with args', function (t) {
const req = new BindRequest({
version: 3,
name: dn.parse('cn=root'),
credentials: 'secret'
})
t.ok(req)
t.equal(req.version, 3)
t.equal(req.name.toString(), 'cn=root')
t.equal(req.credentials, 'secret')
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeInt(3)
ber.writeString('cn=root')
ber.writeString('secret', 0x80)
const req = new BindRequest()
t.ok(req._parse(new BerReader(ber.buffer)))
t.equal(req.version, 3)
t.equal(req.dn.toString(), 'cn=root')
t.equal(req.credentials, 'secret')
t.end()
})
test('toBer', function (t) {
const req = new BindRequest({
messageID: 123,
version: 3,
name: dn.parse('cn=root'),
credentials: 'secret'
})
t.ok(req)
const ber = new BerReader(req.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x60)
t.equal(ber.readInt(), 0x03)
t.equal(ber.readString(), 'cn=root')
t.equal(ber.readString(0x80), 'secret')
t.end()
})

View File

@ -1,56 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { BindResponse } = require('../../lib')
test('new no args', function (t) {
t.ok(new BindResponse())
t.end()
})
test('new with args', function (t) {
const res = new BindResponse({
messageID: 123,
status: 0
})
t.ok(res)
t.equal(res.messageID, 123)
t.equal(res.status, 0)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeEnumeration(0)
ber.writeString('cn=root')
ber.writeString('foo')
const res = new BindResponse()
t.ok(res._parse(new BerReader(ber.buffer)))
t.equal(res.status, 0)
t.equal(res.matchedDN, 'cn=root')
t.equal(res.errorMessage, 'foo')
t.end()
})
test('toBer', function (t) {
const res = new BindResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
})
t.ok(res)
const ber = new BerReader(res.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x61)
t.equal(ber.readEnumeration(), 3)
t.equal(ber.readString(), 'cn=root')
t.equal(ber.readString(), 'foo')
t.end()
})

View File

@ -1,64 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { CompareRequest, dn } = require('../../lib')
test('new no args', function (t) {
t.ok(new CompareRequest())
t.end()
})
test('new with args', function (t) {
const req = new CompareRequest({
entry: dn.parse('cn=foo, o=test'),
attribute: 'sn',
value: 'testy'
})
t.ok(req)
t.equal(req.dn.toString(), 'cn=foo, o=test')
t.equal(req.attribute, 'sn')
t.equal(req.value, 'testy')
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeString('cn=foo, o=test')
ber.startSequence()
ber.writeString('sn')
ber.writeString('testy')
ber.endSequence()
const req = new CompareRequest()
t.ok(req._parse(new BerReader(ber.buffer)))
t.equal(req.dn, 'cn=foo, o=test')
t.equal(req.attribute, 'sn')
t.equal(req.value, 'testy')
t.end()
})
test('toBer', function (t) {
const req = new CompareRequest({
messageID: 123,
entry: dn.parse('cn=foo, o=test'),
attribute: 'sn',
value: 'testy'
})
t.ok(req)
const ber = new BerReader(req.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x6e)
t.equal(ber.readString(), 'cn=foo, o=test')
t.ok(ber.readSequence())
t.equal(ber.readString(), 'sn')
t.equal(ber.readString(), 'testy')
t.end()
})

View File

@ -1,56 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { CompareResponse } = require('../../lib')
test('new no args', function (t) {
t.ok(new CompareResponse())
t.end()
})
test('new with args', function (t) {
const res = new CompareResponse({
messageID: 123,
status: 0
})
t.ok(res)
t.equal(res.messageID, 123)
t.equal(res.status, 0)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeEnumeration(0)
ber.writeString('cn=root')
ber.writeString('foo')
const res = new CompareResponse()
t.ok(res._parse(new BerReader(ber.buffer)))
t.equal(res.status, 0)
t.equal(res.matchedDN, 'cn=root')
t.equal(res.errorMessage, 'foo')
t.end()
})
test('toBer', function (t) {
const res = new CompareResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
})
t.ok(res)
const ber = new BerReader(res.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x6f)
t.equal(ber.readEnumeration(), 3)
t.equal(ber.readString(), 'cn=root')
t.equal(ber.readString(), 'foo')
t.end()
})

View File

@ -1,47 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { DeleteRequest, dn } = require('../../lib')
test('new no args', function (t) {
t.ok(new DeleteRequest())
t.end()
})
test('new with args', function (t) {
const req = new DeleteRequest({
entry: dn.parse('cn=test')
})
t.ok(req)
t.equal(req.dn.toString(), 'cn=test')
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeString('cn=test', 0x4a)
const req = new DeleteRequest()
const reader = new BerReader(ber.buffer)
reader.readSequence(0x4a)
t.ok(req.parse(reader, reader.length))
t.equal(req.dn.toString(), 'cn=test')
t.end()
})
test('toBer', function (t) {
const req = new DeleteRequest({
messageID: 123,
entry: dn.parse('cn=test')
})
t.ok(req)
const ber = new BerReader(req.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readString(0x4a), 'cn=test')
t.end()
})

View File

@ -1,56 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { DeleteResponse } = require('../../lib')
test('new no args', function (t) {
t.ok(new DeleteResponse())
t.end()
})
test('new with args', function (t) {
const res = new DeleteResponse({
messageID: 123,
status: 0
})
t.ok(res)
t.equal(res.messageID, 123)
t.equal(res.status, 0)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeEnumeration(0)
ber.writeString('cn=root')
ber.writeString('foo')
const res = new DeleteResponse()
t.ok(res._parse(new BerReader(ber.buffer)))
t.equal(res.status, 0)
t.equal(res.matchedDN, 'cn=root')
t.equal(res.errorMessage, 'foo')
t.end()
})
test('toBer', function (t) {
const res = new DeleteResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
})
t.ok(res)
const ber = new BerReader(res.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x6b)
t.equal(ber.readEnumeration(), 3)
t.equal(ber.readString(), 'cn=root')
t.equal(ber.readString(), 'foo')
t.end()
})

View File

@ -1,125 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { ExtendedRequest } = require('../../lib')
test('new no args', function (t) {
t.ok(new ExtendedRequest())
t.end()
})
test('new with args', function (t) {
const req = new ExtendedRequest({
requestName: '1.2.3.4',
requestValue: 'test'
})
t.ok(req)
t.equal(req.requestName, '1.2.3.4')
t.equal(req.requestValue, 'test')
t.equal(Buffer.compare(req.requestValueBuffer, Buffer.from('test', 'utf8')), 0)
t.equal(req.value, 'test')
t.equal(Buffer.compare(req.valueBuffer, Buffer.from('test', 'utf8')), 0)
t.end()
})
test('new with buffer args', function (t) {
const req = new ExtendedRequest({
requestName: '1.2.3.4',
requestValue: Buffer.from('test', 'utf8')
})
t.ok(req)
t.equal(req.requestName, '1.2.3.4')
t.equal(req.requestValue, req.requestValueBuffer)
t.equal(Buffer.compare(req.requestValueBuffer, Buffer.from('test', 'utf8')), 0)
t.equal(req.value, req.valueBuffer)
t.equal(Buffer.compare(req.valueBuffer, Buffer.from('test', 'utf8')), 0)
t.end()
})
test('new no args set args', function (t) {
const req = new ExtendedRequest()
t.ok(req)
req.name = '1.2.3.4'
t.equal(req.requestName, '1.2.3.4')
req.value = 'test'
t.equal(req.requestValue, 'test')
t.equal(Buffer.compare(req.requestValueBuffer, Buffer.from('test', 'utf8')), 0)
t.equal(req.value, 'test')
t.equal(Buffer.compare(req.valueBuffer, Buffer.from('test', 'utf8')), 0)
t.end()
})
test('new no args set args buffer', function (t) {
const req = new ExtendedRequest()
t.ok(req)
req.name = '1.2.3.4'
t.equal(req.requestName, '1.2.3.4')
req.value = Buffer.from('test', 'utf8')
t.equal(req.requestValue, req.requestValueBuffer)
t.equal(Buffer.compare(req.requestValueBuffer, Buffer.from('test', 'utf8')), 0)
t.equal(req.value, req.valueBuffer)
t.equal(Buffer.compare(req.valueBuffer, Buffer.from('test', 'utf8')), 0)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeString('1.2.3.4', 0x80)
ber.writeString('test', 0x81)
const req = new ExtendedRequest()
t.ok(req._parse(new BerReader(ber.buffer)))
t.equal(req.requestName, '1.2.3.4')
t.equal(req.requestValue, 'test')
t.equal(Buffer.compare(req.requestValueBuffer, Buffer.from('test', 'utf8')), 0)
t.equal(req.value, 'test')
t.equal(Buffer.compare(req.valueBuffer, Buffer.from('test', 'utf8')), 0)
t.end()
})
test('toBer', function (t) {
const req = new ExtendedRequest({
messageID: 123,
requestName: '1.2.3.4',
requestValue: 'test'
})
t.ok(req)
const ber = new BerReader(req.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x77)
t.equal(ber.readString(0x80), '1.2.3.4')
t.equal(ber.readString(0x81), 'test')
t.end()
})
test('toBer from buffer', function (t) {
const req = new ExtendedRequest({
messageID: 123,
requestName: '1.2.3.4',
requestValue: Buffer.from('test', 'utf8')
})
t.ok(req)
const ber = new BerReader(req.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x77)
t.equal(ber.readString(0x80), '1.2.3.4')
t.equal(ber.readString(0x81), 'test')
t.end()
})

View File

@ -1,68 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { ExtendedResponse } = require('../../lib')
test('new no args', function (t) {
t.ok(new ExtendedResponse())
t.end()
})
test('new with args', function (t) {
const res = new ExtendedResponse({
messageID: 123,
status: 0,
responseName: '1.2.3.4',
responseValue: 'test'
})
t.ok(res)
t.equal(res.messageID, 123)
t.equal(res.status, 0)
t.equal(res.responseName, '1.2.3.4')
t.equal(res.responseValue, 'test')
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeEnumeration(0)
ber.writeString('cn=root')
ber.writeString('foo')
ber.writeString('1.2.3.4', 0x8a)
ber.writeString('test', 0x8b)
const res = new ExtendedResponse()
t.ok(res._parse(new BerReader(ber.buffer)))
t.equal(res.status, 0)
t.equal(res.matchedDN, 'cn=root')
t.equal(res.errorMessage, 'foo')
t.equal(res.responseName, '1.2.3.4')
t.equal(res.responseValue, 'test')
t.end()
})
test('toBer', function (t) {
const res = new ExtendedResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo',
responseName: '1.2.3.4',
responseValue: 'test'
})
t.ok(res)
const ber = new BerReader(res.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x78)
t.equal(ber.readEnumeration(), 3)
t.equal(ber.readString(), 'cn=root')
t.equal(ber.readString(), 'foo')
t.equal(ber.readString(0x8a), '1.2.3.4')
t.equal(ber.readString(0x8b), 'test')
t.end()
})

View File

@ -1,60 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { ModifyDNRequest, dn } = require('../../lib')
test('new no args', function (t) {
t.ok(new ModifyDNRequest())
t.end()
})
test('new with args', function (t) {
const req = new ModifyDNRequest({
entry: dn.parse('cn=foo, o=test'),
newRdn: dn.parse('cn=foo2'),
deleteOldRdn: true
})
t.ok(req)
t.equal(req.dn.toString(), 'cn=foo, o=test')
t.equal(req.newRdn.toString(), 'cn=foo2')
t.equal(req.deleteOldRdn, true)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeString('cn=foo, o=test')
ber.writeString('cn=foo2')
ber.writeBoolean(true)
const req = new ModifyDNRequest()
t.ok(req._parse(new BerReader(ber.buffer)))
t.equal(req.dn.toString(), 'cn=foo, o=test')
t.equal(req.newRdn.toString(), 'cn=foo2')
t.equal(req.deleteOldRdn, true)
t.end()
})
test('toBer', function (t) {
const req = new ModifyDNRequest({
messageID: 123,
entry: dn.parse('cn=foo, o=test'),
newRdn: dn.parse('cn=foo2'),
deleteOldRdn: true
})
t.ok(req)
const ber = new BerReader(req.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x6c)
t.equal(ber.readString(), 'cn=foo, o=test')
t.equal(ber.readString(), 'cn=foo2')
t.equal(ber.readBoolean(), true)
t.end()
})

View File

@ -1,56 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { ModifyDNResponse } = require('../../lib')
test('new no args', function (t) {
t.ok(new ModifyDNResponse())
t.end()
})
test('new with args', function (t) {
const res = new ModifyDNResponse({
messageID: 123,
status: 0
})
t.ok(res)
t.equal(res.messageID, 123)
t.equal(res.status, 0)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeEnumeration(0)
ber.writeString('cn=root')
ber.writeString('foo')
const res = new ModifyDNResponse()
t.ok(res._parse(new BerReader(ber.buffer)))
t.equal(res.status, 0)
t.equal(res.matchedDN, 'cn=root')
t.equal(res.errorMessage, 'foo')
t.end()
})
test('toBer', function (t) {
const res = new ModifyDNResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
})
t.ok(res)
const ber = new BerReader(res.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x6d)
t.equal(ber.readEnumeration(), 3)
t.equal(ber.readString(), 'cn=root')
t.equal(ber.readString(), 'foo')
t.end()
})

View File

@ -1,86 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { ModifyRequest, Attribute, Change, dn } = require('../../lib')
test('new no args', function (t) {
t.ok(new ModifyRequest())
t.end()
})
test('new with args', function (t) {
const req = new ModifyRequest({
object: dn.parse('cn=foo, o=test'),
changes: [new Change({
operation: 'Replace',
modification: new Attribute({ type: 'objectclass', vals: ['person'] })
})]
})
t.ok(req)
t.equal(req.dn.toString(), 'cn=foo, o=test')
t.equal(req.changes.length, 1)
t.equal(req.changes[0].operation, 'replace')
t.equal(req.changes[0].modification.type, 'objectclass')
t.equal(req.changes[0].modification.vals[0], 'person')
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeString('cn=foo, o=test')
ber.startSequence()
ber.startSequence()
ber.writeEnumeration(0x02)
ber.startSequence()
ber.writeString('objectclass')
ber.startSequence(0x31)
ber.writeString('person')
ber.endSequence()
ber.endSequence()
ber.endSequence()
ber.endSequence()
const req = new ModifyRequest()
t.ok(req._parse(new BerReader(ber.buffer)))
t.equal(req.dn.toString(), 'cn=foo, o=test')
t.equal(req.changes.length, 1)
t.equal(req.changes[0].operation, 'replace')
t.equal(req.changes[0].modification.type, 'objectclass')
t.equal(req.changes[0].modification.vals[0], 'person')
t.end()
})
test('toBer', function (t) {
const req = new ModifyRequest({
messageID: 123,
object: dn.parse('cn=foo, o=test'),
changes: [new Change({
operation: 'Replace',
modification: new Attribute({ type: 'objectclass', vals: ['person'] })
})]
})
t.ok(req)
const ber = new BerReader(req.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x66)
t.equal(ber.readString(), 'cn=foo, o=test')
t.ok(ber.readSequence())
t.ok(ber.readSequence())
t.equal(ber.readEnumeration(), 0x02)
t.ok(ber.readSequence())
t.equal(ber.readString(), 'objectclass')
t.equal(ber.readSequence(), 0x31)
t.equal(ber.readString(), 'person')
t.end()
})

View File

@ -1,56 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { ModifyResponse } = require('../../lib')
test('new no args', function (t) {
t.ok(new ModifyResponse())
t.end()
})
test('new with args', function (t) {
const res = new ModifyResponse({
messageID: 123,
status: 0
})
t.ok(res)
t.equal(res.messageID, 123)
t.equal(res.status, 0)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeEnumeration(0)
ber.writeString('cn=root')
ber.writeString('foo')
const res = new ModifyResponse()
t.ok(res._parse(new BerReader(ber.buffer)))
t.equal(res.status, 0)
t.equal(res.matchedDN, 'cn=root')
t.equal(res.errorMessage, 'foo')
t.end()
})
test('toBer', function (t) {
const res = new ModifyResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
})
t.ok(res)
const ber = new BerReader(res.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x67)
t.equal(ber.readEnumeration(), 3)
t.equal(ber.readString(), 'cn=root')
t.equal(ber.readString(), 'foo')
t.end()
})

View File

@ -1,7 +1,7 @@
'use strict'
const { test } = require('tap')
const { Parser, LDAPMessage, LDAP_REQ_EXTENSION } = require('../../lib')
const { Parser } = require('../../lib')
test('wrong protocol error', function (t) {
const p = new Parser()
@ -14,36 +14,3 @@ test('wrong protocol error', function (t) {
// Send some bogus data to incur an error
p.write(Buffer.from([16, 1, 4]))
})
test('bad protocol op', function (t) {
const p = new Parser()
const message = new LDAPMessage({
protocolOp: 254 // bogus (at least today)
})
p.once('error', function (err) {
t.ok(err)
t.ok(/not supported$/.test(err.message))
t.end()
})
p.write(message.toBer())
})
test('bad message structure', function (t) {
const p = new Parser()
// message with bogus structure
const message = new LDAPMessage({
protocolOp: LDAP_REQ_EXTENSION
})
message._toBer = function (writer) {
writer.writeBuffer(Buffer.from([16, 1, 4]), 80)
return writer
}
p.once('error', function (err) {
t.ok(err)
t.end()
})
p.write(message.toBer())
})

View File

@ -1,91 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { SearchEntry, Attribute, dn } = require('../../lib')
test('new no args', function (t) {
t.ok(new SearchEntry())
t.end()
})
test('new with args', function (t) {
const res = new SearchEntry({
messageID: 123,
objectName: dn.parse('cn=foo, o=test'),
attributes: [new Attribute({ type: 'cn', vals: ['foo'] }),
new Attribute({ type: 'objectclass', vals: ['person'] })]
})
t.ok(res)
t.equal(res.messageID, 123)
t.equal(res.dn.toString(), 'cn=foo, o=test')
t.equal(res.attributes.length, 2)
t.equal(res.attributes[0].type, 'cn')
t.equal(res.attributes[0].vals[0], 'foo')
t.equal(res.attributes[1].type, 'objectclass')
t.equal(res.attributes[1].vals[0], 'person')
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeString('cn=foo, o=test')
ber.startSequence()
ber.startSequence()
ber.writeString('cn')
ber.startSequence(0x31)
ber.writeString('foo')
ber.endSequence()
ber.endSequence()
ber.startSequence()
ber.writeString('objectclass')
ber.startSequence(0x31)
ber.writeString('person')
ber.endSequence()
ber.endSequence()
ber.endSequence()
const res = new SearchEntry()
t.ok(res._parse(new BerReader(ber.buffer)))
t.equal(res.dn, 'cn=foo, o=test')
t.equal(res.attributes.length, 2)
t.equal(res.attributes[0].type, 'cn')
t.equal(res.attributes[0].vals[0], 'foo')
t.equal(res.attributes[1].type, 'objectclass')
t.equal(res.attributes[1].vals[0], 'person')
t.end()
})
test('toBer', function (t) {
const res = new SearchEntry({
messageID: 123,
objectName: dn.parse('cn=foo, o=test'),
attributes: [new Attribute({ type: 'cn', vals: ['foo'] }),
new Attribute({ type: 'objectclass', vals: ['person'] })]
})
t.ok(res)
const ber = new BerReader(res.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x64)
t.equal(ber.readString(), 'cn=foo,o=test')
t.ok(ber.readSequence())
t.ok(ber.readSequence())
t.equal(ber.readString(), 'cn')
t.equal(ber.readSequence(), 0x31)
t.equal(ber.readString(), 'foo')
t.ok(ber.readSequence())
t.equal(ber.readString(), 'objectclass')
t.equal(ber.readSequence(), 0x31)
t.equal(ber.readString(), 'person')
t.end()
})

View File

@ -1,98 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { SearchRequest, EqualityFilter, dn } = require('../../lib')
test('new no args', function (t) {
t.ok(new SearchRequest())
t.end()
})
test('new with args', function (t) {
const req = new SearchRequest({
baseObject: dn.parse('cn=foo, o=test'),
filter: new EqualityFilter({
attribute: 'email',
value: 'foo@bar.com'
}),
attributes: ['cn', 'sn']
})
t.ok(req)
t.equal(req.dn.toString(), 'cn=foo, o=test')
t.equal(req.filter.toString(), '(email=foo@bar.com)')
t.equal(req.attributes.length, 2)
t.equal(req.attributes[0], 'cn')
t.equal(req.attributes[1], 'sn')
t.end()
})
test('parse', function (t) {
const f = new EqualityFilter({
attribute: 'email',
value: 'foo@bar.com'
})
const ber = new BerWriter()
ber.writeString('cn=foo, o=test')
ber.writeEnumeration(0)
ber.writeEnumeration(0)
ber.writeInt(1)
ber.writeInt(2)
ber.writeBoolean(false)
const eqBer = f.toBer()
ber.appendBuffer(eqBer.buffer)
const req = new SearchRequest()
t.ok(req._parse(new BerReader(ber.buffer)))
t.equal(req.dn.toString(), 'cn=foo, o=test')
t.equal(req.scope, 'base')
t.equal(req.derefAliases, 0)
t.equal(req.sizeLimit, 1)
t.equal(req.timeLimit, 2)
t.equal(req.typesOnly, false)
t.equal(req.filter.toString(), '(email=foo@bar.com)')
t.equal(req.attributes.length, 0)
t.end()
})
test('toBer', function (t) {
const req = new SearchRequest({
messageID: 123,
baseObject: dn.parse('cn=foo, o=test'),
scope: 1,
derefAliases: 2,
sizeLimit: 10,
timeLimit: 20,
typesOnly: true,
filter: new EqualityFilter({
attribute: 'email',
value: 'foo@bar.com'
}),
attributes: ['cn', 'sn']
})
t.ok(req)
const ber = new BerReader(req.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x63)
// Make sure we've removed spaces from between RDNs:
t.equal(ber.readString(), 'cn=foo,o=test')
t.equal(ber.readEnumeration(), 1)
t.equal(ber.readEnumeration(), 2)
t.equal(ber.readInt(), 10)
t.equal(ber.readInt(), 20)
t.ok(ber.readBoolean())
t.equal(ber.readSequence(), 0xa3)
t.equal(ber.readString(), 'email')
t.equal(ber.readString(), 'foo@bar.com')
t.ok(ber.readSequence())
t.equal(ber.readString(), 'cn')
t.equal(ber.readString(), 'sn')
t.end()
})

View File

@ -1,56 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { SearchResponse } = require('../../lib')
test('new no args', function (t) {
t.ok(new SearchResponse())
t.end()
})
test('new with args', function (t) {
const res = new SearchResponse({
messageID: 123,
status: 0
})
t.ok(res)
t.equal(res.messageID, 123)
t.equal(res.status, 0)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
ber.writeEnumeration(0)
ber.writeString('cn=root')
ber.writeString('foo')
const res = new SearchResponse()
t.ok(res._parse(new BerReader(ber.buffer)))
t.equal(res.status, 0)
t.equal(res.matchedDN, 'cn=root')
t.equal(res.errorMessage, 'foo')
t.end()
})
test('toBer', function (t) {
const res = new SearchResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
})
t.ok(res)
const ber = new BerReader(res.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.equal(ber.readSequence(), 0x65)
t.equal(ber.readEnumeration(), 3)
t.equal(ber.readString(), 'cn=root')
t.equal(ber.readString(), 'foo')
t.end()
})

View File

@ -1,37 +0,0 @@
'use strict'
const { test } = require('tap')
const { BerReader, BerWriter } = require('@ldapjs/asn1')
const { UnbindRequest } = require('../../lib')
test('new no args', function (t) {
t.ok(new UnbindRequest())
t.end()
})
test('new with args', function (t) {
const req = new UnbindRequest({})
t.ok(req)
t.end()
})
test('parse', function (t) {
const ber = new BerWriter()
const req = new UnbindRequest()
t.ok(req._parse(new BerReader(ber.buffer)))
t.end()
})
test('toBer', function (t) {
const req = new UnbindRequest({
messageID: 123
})
t.ok(req)
const ber = new BerReader(req.toBer())
t.ok(ber)
t.equal(ber.readSequence(), 0x30)
t.equal(ber.readInt(), 123)
t.end()
})

View File

@ -235,7 +235,7 @@ tap.test('bind/unbind identity anonymous', function (t) {
return next()
})
const anonDN = ldap.dn.parse('cn=anonymous')
const anonDN = ldap.parseDN('cn=anonymous')
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
@ -276,8 +276,8 @@ tap.test('bind/unbind identity user', function (t) {
return next()
})
const anonDN = ldap.dn.parse('cn=anonymous')
const testDN = ldap.dn.parse('cn=anotheruser')
const anonDN = ldap.parseDN('cn=anonymous')
const testDN = ldap.parseDN('cn=anotheruser')
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
@ -316,9 +316,7 @@ tap.test('strict routing', function (t) {
vasync.pipeline({
funcs: [
function setup (_, cb) {
server = ldap.createServer({
// strictDN: true - on by default
})
server = ldap.createServer({})
// invalid DNs would go to default handler
server.search('', function (req, res, next) {
t.ok(req.dn)
@ -330,26 +328,11 @@ tap.test('strict routing', function (t) {
server.listen(sock, function () {
t.ok(true, 'server startup')
clt = ldap.createClient({
socketPath: sock,
strictDN: false
socketPath: sock
})
cb()
})
},
function testBad (_, cb) {
clt.search('not a dn', { scope: 'base' }, function (err, res) {
t.error(err)
res.once('error', function (err2) {
t.ok(err2)
t.equal(err2.code, ldap.LDAP_INVALID_DN_SYNTAX)
cb()
})
res.once('end', function () {
t.fail('accepted invalid dn')
cb(Error('bogus'))
})
})
},
function testGood (_, cb) {
clt.search(testDN, { scope: 'base' }, function (err, res) {
t.error(err)
@ -373,37 +356,6 @@ tap.test('strict routing', function (t) {
})
})
tap.test('non-strict routing', function (t) {
const server = ldap.createServer({
strictDN: false
})
const testDN = 'this ain\'t a DN'
// invalid DNs go to default handler
server.search('', function (req, res, next) {
t.ok(req.dn)
t.equal(typeof (req.dn), 'string')
t.equal(req.dn, testDN)
res.end()
next()
})
server.listen(t.context.sock, function () {
t.ok(true, 'server startup')
const clt = ldap.createClient({
socketPath: t.context.sock,
strictDN: false
})
clt.search(testDN, { scope: 'base' }, function (err, res) {
t.error(err)
res.on('end', function () {
clt.destroy()
server.close(() => t.end())
})
})
})
})
tap.test('close accept a callback', function (t) {
const server = ldap.createServer()
// callback is called when the server is closed