Initial working client/server version

This commit is contained in:
Mark Cavage 2011-08-04 13:32:01 -07:00
commit ca1443f102
82 changed files with 8304 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
*.log
*.ldif

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2011 Mark Cavage, All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE

13
README.md Normal file
View File

@ -0,0 +1,13 @@
node-ldapjs will blow your mind. Docs coming soon.
## Installation
npm install ldapjs
## License
MIT.
## Bugs
See <https://github.com/mcavage/node-ldapjs/issues>.

104
lib/attribute.js Normal file
View File

@ -0,0 +1,104 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var Protocol = require('./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');
if (options.vals && !Array.isArray(options.vals))
throw new TypeErrr('options.vals must be an array[string]');
if (options.vals && options.vals.length) {
options.vals.forEach(function(v) {
if (typeof(v) !== 'string')
throw new TypeErrr('options.vals must be an array[string]');
});
}
} else {
options = {};
}
this.type = options.type || '';
this.vals = options.vals ? options.vals.slice(0) : [];
var self = this;
this.__defineGetter__('json', function() {
return {
type: self.type,
vals: self.vals
};
});
}
module.exports = Attribute;
Attribute.prototype.addValue = function(val) {
if (typeof(val) !== 'string')
throw new TypeError('val (string) required');
this.vals.push(val);
};
Attribute.prototype.parse = function(ber) {
assert.ok(ber);
ber.readSequence();
this.type = ber.readString();
if (ber.readSequence(Protocol.LBER_SET)) {
var end = ber.offset + ber.length;
while (ber.offset < end) {
var val = ber.readString();
this.vals.push(val);
}
}
return true;
};
Attribute.prototype.toBer = function(ber) {
assert.ok(ber);
ber.startSequence();
ber.writeString(this.type);
if (this.vals && this.vals.length) {
ber.startSequence(Protocol.LBER_SET);
ber.writeStringArray(this.vals);
ber.endSequence();
}
ber.endSequence();
return ber;
};
Attribute.toBer = function(attr, ber) {
return Attribute.prototype.toBer.call(attr, ber);
};
Attribute.isAttribute = function(attr) {
if (typeof(attr) !== 'object') return false;
if (attr instanceof Attribute) return true;
if (!attr.type || typeof(attr.type) !== 'string') return false;
if (!attr.vals || !Array.isArray(attr.vals)) return false;
for (var i = 0; i < attr.vals.length; i++)
if (typeof(attr.vals[i]) !== 'string') return false;
return true;
};
Attribute.prototype.toString = function() {
return JSON.stringify(this.json);
};

86
lib/change.js Normal file
View File

@ -0,0 +1,86 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var Attribute = require('./attribute');
var Protocol = require('./protocol');
///--- API
function Change(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.operation && typeof(options.operation) !== 'string')
throw new TypeError('options.operation must be a string');
if (options.modification && !(options.modification instanceof Attribute))
throw new TypeErrr('options.modification must be an Attribute');
} else {
options = {};
}
var self = this;
this.__defineGetter__('operation', function() {
switch (self._operation) {
case 0x00: return 'Add';
case 0x01: return 'Delete';
case 0x02: return 'Replace';
default: return 'Invalid';
}
});
this.__defineSetter__('operation', function(val) {
if (typeof(val) !== 'string')
throw new TypeError('operation must be a string');
switch (val.toLowerCase()) {
case 'add':
self._operation = 0x00;
break;
case 'delete':
self._operation = 0x01;
break;
case 'replace':
self._operation = 0x02;
break;
default:
throw new Error('Invalid operation type: 0x' + val.toString(16));
}
});
this.__defineGetter__('json', function() {
return {
operation: self.operation,
modification: self.modification ? self.modification.json : {}
};
});
this.operation = options.operation || 'add';
this.modification = options.modification || null;
}
module.exports = Change;
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;
};

726
lib/client.js Normal file
View File

@ -0,0 +1,726 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var EventEmitter = require('events').EventEmitter;
var net = require('net');
var tls = require('tls');
var util = require('util');
var Attribute = require('./attribute');
var Change = require('./change');
var Control = require('./control');
var Protocol = require('./protocol');
var dn = require('./dn');
var errors = require('./errors');
var filters = require('./filters');
var logStub = require('./log_stub');
var messages = require('./messages');
var url = require('./url');
///--- Globals
var AddRequest = messages.AddRequest;
var BindRequest = messages.BindRequest;
var CompareRequest = messages.CompareRequest;
var DeleteRequest = messages.DeleteRequest;
var ExtendedRequest = messages.ExtendedRequest;
var ModifyRequest = messages.ModifyRequest;
var ModifyDNRequest = messages.ModifyDNRequest;
var SearchRequest = messages.SearchRequest;
var UnbindRequest = messages.UnbindRequest;
var LDAPResult = messages.LDAPResult;
var SearchEntry = messages.SearchEntry;
var SearchResponse = messages.SearchResponse;
var Parser = messages.Parser;
var Filter = filters.Filter;
var PresenceFilter = filters.PresenceFilter;
var MAX_MSGID = Math.pow(2, 31) - 1;
///--- Internal Helpers
function xor() {
var b = false;
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] && !b) b = true;
else if (arguments[i] && b) return false;
}
return b;
}
function validateControls(controls) {
if (Array.isArray(controls)) {
controls.forEach(function(c) {
if (!(c instanceof Control))
throw new TypeError('controls must be [Control]');
});
} else if (controls instanceof Control) {
controls = [controls];
} else {
throw new TypeError('controls must be [Control]');
}
return controls;
}
function DisconnectedError(message) {
Error.call(this, message);
if (Error.captureStackTrace)
Error.captureStackTrace(this, DisconnectedError);
}
util.inherits(DisconnectedError, Error);
///--- API
/**
* Constructs a new client.
*
* The options object is required, and must contain either a URL (string) or
* a socketPath (string); the socketPath is only if you want to talk to an LDAP
* server over a Unix Domain Socket. Additionally, you can pass in a log4js
* option that is the result of `require('log4js')`, presumably after you've
* configured it.
*
* @param {Object} options must have either url or socketPath.
* @throws {TypeError} on bad input.
*/
function Client(options) {
if (!options || typeof(options) !== 'object')
throw new TypeError('options (object) required');
if (options.url && typeof(options.url) !== 'string')
throw new TypeError('options.url (string) required');
if (options.socketPath && typeof(options.socketPath) !== 'string')
throw new TypeError('options.socketPath must be a string');
if (options.log4js && typeof(options.log4js) !== 'object')
throw new TypeError('options.log4s must be an object');
if (options.numConnections && typeof(options.numConnections) !== 'number')
throw new TypeError('options.numConnections must be a number');
if (!xor(options.url, options.socketPath))
throw new TypeError('options.url ^ options.socketPath required');
EventEmitter.call(this, options);
var self = this;
this.secure = false;
if (options.url) {
this.url = url.parse(options.url);
this.secure = this.url.secure;
}
this.log4js = options.log4js || logStub;
this.numConnections = Math.abs(options.numConnections) || 3;
this.connections = [];
this.currentConnection = 0;
this.connectOptions = options.socketPath ? options.socketPath : {
port: self.url.port,
host: self.url.hostname
};
this.shutdown = false;
this.__defineGetter__('log', function() {
if (!self._log)
self._log = self.log4js.getLogger('LDAPClient');
return self._log;
});
// Build the connection pool
function newConnection() {
var c;
if (self.secure) {
c = tls.createConnection(self.connectOptions);
} else {
c = net.createConnection(self.connectOptions);
}
assert.ok(c);
c.parser = new Parser({
log4js: self.log4js
});
// Wrap the events
c.ldap = {
id: options.socketPath || self.url.hostname,
connected: true, // lie, but node queues for us
messageID: 0,
messages: {}
};
c.ldap.__defineGetter__('nextMessageID', function() {
if (++c.ldap.messageID >= MAX_MSGID)
c.ldap.messageID = 1;
return c.ldap.messageID;
});
c.on('connect', function() {
c.ldap.connected = true;
c.ldap.id += ':' + (c.type !== 'unix' ? c.remotePort : c.fd);
self.emit('connect', c.ldap.id);
});
c.on('end', function() {
self.log.trace('%s end', c.ldap.id);
c.ldap.connected = false;
if (!self.shutdown)
c.connect();
});
c.addListener('close', function(had_err) {
self.log.trace('%s close; had_err=%j', c.ldap.id, had_err);
c.ldap.connected = false;
if (!self.shutdown)
c.connect();
});
c.on('error', function(err) {
self.log.warn('%s unexpected connection error %s', c.ldap.id, err);
self.emit('error', err, c.ldap.id);
c.ldap.connected = false;
if (!self.shutdown) {
c.end();
c.connect();
}
});
c.on('timeout', function() {
self.log.trace('%s timed out', c.ldap.id);
c.ldap.connected = false;
if (!self.shutdown) {
c.end();
c.connect();
}
});
c.on('data', function(data) {
if (self.log.isTraceEnabled())
self.log.trace('data on %s: %s', c.ldap.id, util.inspect(data));
c.parser.write(data);
});
// The "router"
c.parser.on('message', function(message) {
message.connection = c;
var callback = c.ldap.messages[message.messageID];
if (!callback) {
self.log.error('%s: received unsolicited message: %j',
c.ldap.id, message.json);
return;
}
return callback(message);
});
return c;
}
for (var i = 0; i < this.numConnections; i++) {
self.connections.push(newConnection());
}
}
util.inherits(Client, EventEmitter);
module.exports = Client;
/**
* Performs a simple authentication against the server.
*
* @param {String} name the DN to bind as.
* @param {String} credentials the userPassword associated with name.
* @param {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err, res).
* @throws {TypeError} on invalid input.
*/
Client.prototype.bind = function(name, credentials, controls, callback) {
if (typeof(name) !== 'string')
throw new TypeError('name (string) required');
if (typeof(credentials) !== 'string')
throw new TypeError('credentials (string) required');
if (typeof(controls) === 'function') {
callback = controls;
controls = [];
} else {
control = validateControls(controls);
}
if (typeof(callback) !== 'function')
throw new TypeError('callback (function) required');
var self = this;
var req = new BindRequest({
name: dn.parse(name),
authentication: 'Simple',
credentials: credentials,
controls: controls
});
var cbIssued = false;
var finished = 0;
function _callback(err, res) {
if (err) {
if (!cbIssued) {
cbIssued = true;
return callback(err);
}
}
if (++finished >= self.connections.length && !cbIssued) {
cbIssued = true;
return callback(null, res);
}
}
this.connections.forEach(function(c) {
return self._send(req, [errors.LDAP_SUCCESS], _callback, c);
});
};
/**
* Adds an entry to the LDAP server.
*
* @param {String} name the DN of the entry to add.
* @param {Array} attributes an array of Attributes to be added.
* @param {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err, res).
* @throws {TypeError} on invalid input.
*/
Client.prototype.add = function(name, attributes, controls, callback) {
if (typeof(name) !== 'string')
throw new TypeError('name (string) required');
if (!Array.isArray(attributes))
throw new TypeError('attributes ([Attribute]) required');
attributes.forEach(function(a) {
if (!Attribute.isAttribute(a))
throw new TypeError('attributes ([Attribute]) required');
});
if (typeof(controls) === 'function') {
callback = controls;
controls = [];
} else {
control = validateControls(controls);
}
if (typeof(callback) !== 'function')
throw new TypeError('callback (function) required');
var req = new AddRequest({
entry: dn.parse(name),
attributes: attributes,
controls: controls
});
return this._send(req, [errors.LDAP_SUCCESS], callback);
};
/**
* Compares an attribute/value pair with an entry on the LDAP server.
*
* @param {String} name the DN of the entry to compare attributes with.
* @param {String} attribute name of an attribute to check.
* @param {String} value value of an attribute to check.
* @param {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err, boolean, res).
* @throws {TypeError} on invalid input.
*/
Client.prototype.compare = function(name,
attribute,
value,
controls,
callback) {
if (typeof(name) !== 'string')
throw new TypeError('name (string) required');
if (typeof(attribute) !== 'string')
throw new TypeError('attribute (string) required');
if (typeof(value) !== 'string')
throw new TypeError('value (string) required');
if (typeof(controls) === 'function') {
callback = controls;
controls = [];
} else {
control = validateControls(controls);
}
if (typeof(callback) !== 'function')
throw new TypeError('callback (function) required');
var req = new CompareRequest({
entry: dn.parse(name),
attribute: attribute,
value: value,
controls: controls
});
function _callback(err, res) {
if (err)
return callback(err);
return callback(null, (res.status === errors.LDAP_COMPARE_TRUE), res);
}
return this._send(req,
[errors.LDAP_COMPARE_TRUE, errors.LDAP_COMPARE_FALSE],
_callback);
};
/**
* Deletes an entry from the LDAP server.
*
* @param {String} name the DN of the entry to delete.
* @param {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err, res).
* @throws {TypeError} on invalid input.
*/
Client.prototype.del = function(name, controls, callback) {
if (typeof(name) !== 'string')
throw new TypeError('name (string) required');
if (typeof(controls) === 'function') {
callback = controls;
controls = [];
} else {
control = validateControls(controls);
}
if (typeof(callback) !== 'function')
throw new TypeError('callback (function) required');
var req = new DeleteRequest({
entry: dn.parse(name),
controls: controls
});
return this._send(req, [errors.LDAP_SUCCESS], callback);
};
/**
* Performs an extended operation on the LDAP server.
*
* Pretty much none of the LDAP extended operations return an OID
* (responseName), so I just don't bother giving it back in the callback.
* It's on the third param in `res` if you need it.
*
* @param {String} name the OID of the extended operation to perform.
* @param {String} value value to pass in for this operation.
* @param {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err, value, res).
* @throws {TypeError} on invalid input.
*/
Client.prototype.exop = function(name, value, controls, callback) {
if (typeof(name) !== 'string')
throw new TypeError('name (string) required');
if (typeof(value) === 'function') {
callback = value;
controls = [];
value = '';
}
if (typeof(value) !== 'string')
throw new TypeError('value (string) required');
if (typeof(controls) === 'function') {
callback = controls;
controls = [];
} else {
control = validateControls(controls);
}
if (typeof(callback) !== 'function')
throw new TypeError('callback (function) required');
var req = new ExtendedRequest({
requestName: name,
requestValue: value,
controls: controls
});
function _callback(err, res) {
if (err)
return callback(err);
return callback(null, res.responseValue || '', res);
}
return this._send(req, [errors.LDAP_SUCCESS], _callback);
};
/**
* Performs an LDAP modify against the server.
*
* @param {String} name the DN of the entry to modify.
* @param {Change} change update to perform (can be [Change]).
* @param {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err, res).
* @throws {TypeError} on invalid input.
*/
Client.prototype.modify = function(name, change, controls, callback) {
if (typeof(name) !== 'string')
throw new TypeError('name (string) required');
if (!Array.isArray(change) && !(change instanceof Change))
throw new TypeError('change (Change) required');
if (!Array.isArray(change)) {
var save = change;
change = [];
change.push(save);
}
change.forEach(function(c) {
if (!(c instanceof Change))
throw new TypeError('change ([Change]) required');
});
if (typeof(controls) === 'function') {
callback = controls;
controls = [];
} else {
control = validateControls(controls);
}
if (typeof(callback) !== 'function')
throw new TypeError('callback (function) required');
var req = new ModifyRequest({
object: dn.parse(name),
changes: change,
controls: controls
});
return this._send(req, [errors.LDAP_SUCCESS], callback);
};
/**
* Performs an LDAP modifyDN against the server.
*
* This does not allow you to keep the old DN, as while the LDAP protocol
* has a facility for that, it's stupid. Just Search/Add.
*
* This will automatically deal with "new superior" logic.
*
* @param {String} name the DN of the entry to modify.
* @param {String} newName the new DN to move this entry to.
* @param {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err, res).
* @throws {TypeError} on invalid input.
*/
Client.prototype.modifyDN = function(name, newName, controls, callback) {
if (typeof(name) !== 'string')
throw new TypeError('name (string) required');
if (typeof(newName) !== 'string')
throw new TypeError('newName (string) required');
if (typeof(controls) === 'function') {
callback = controls;
controls = [];
} else {
control = validateControls(controls);
}
if (typeof(callback) !== 'function')
throw new TypeError('callback (function) required');
var DN = dn.parse(name);
var newDN = dn.parse(newName);
var req = new ModifyDNRequest({
entry: DN,
deleteOldRdn: true,
controls: controls
});
if (newDN.length !== 1) {
req.newRdn = dn.parse(newDN.rdns.shift().toString());
req.newSuperior = newDN;
} else {
req.newRdn = newDN;
}
return this._send(req, [errors.LDAP_SUCCESS], callback);
};
/**
* Performs an LDAP search against the server.
*
* Note that the defaults for options are a 'base' search, if that's what
* you want you can just pass in a string for options and it will be treated
* as the search filter. Also, you can either pass in programatic Filter
* objects or a filter string as the filter option.
*
* Note that this method is 'special' in that the callback 'res' param will
* have two important events on it, namely 'entry' and 'end' that you can hook
* to. The former will emit a SearchEntry object for each record that comes
* back, and the latter will emit a normal LDAPResult object.
*
* @param {String} base the DN in the tree to start searching at.
* @param {Object} options parameters:
* - {String} scope default of 'base'.
* - {String} filter default of '(objectclass=*)'.
* - {Array} attributes [string] to return.
* - {Boolean} attrsOnly whether to return values.
* @param {Control} controls (optional) either a Control or [Control].
* @param {Function} callback of the form f(err, res).
* @throws {TypeError} on invalid input.
*/
Client.prototype.search = function(base, options, controls, callback) {
if (typeof(base) !== 'string')
throw new TypeError('base (string) required');
if (Array.isArray(options) || (options instanceof Control)) {
controls = options;
options = {};
} else if (typeof(options) === 'function') {
callback = options;
controls = [];
options = {
filter: new PresenceFilter({attribute: 'objectclass'})
};
} else if (typeof(options) === 'string') {
options = {filter: filters.parseString(options)};
} else if (typeof(options) !== 'object') {
throw new TypeError('options (object) required');
}
if (!(options.filter instanceof Filter))
throw new TypeError('options.filter (Filter) required');
if (typeof(controls) === 'function') {
callback = controls;
controls = [];
} else {
control = validateControls(controls);
}
if (typeof(callback) !== 'function')
throw new TypeError('callback (function) required');
var req = new SearchRequest({
baseObject: dn.parse(base),
scope: options.scope || 'base',
filter: options.filter,
derefAliases: Protocol.NEVER_DEREF_ALIASES,
sizeLimit: options.sizeLimit || 0,
timeLimit: options.timeLimit || 10,
typesOnly: options.typesOnly || false,
attributes: options.attributes || []
});
var res = new EventEmitter();
this._send(req, [errors.LDAP_SUCCESS], res);
return callback(null, res);
};
/**
* Unbinds this client from the LDAP server.
*
* Note that unbind does not have a response, so this callback is actually
* optional; either way, the client is disconnected.
*
* @param {Function} callback of the form f(err).
* @throws {TypeError} if you pass in callback as not a function.
*/
Client.prototype.unbind = function(callback) {
if (callback && typeof(callback) !== 'function')
throw new TypeError('callback must be a function');
var self = this;
if (!callback)
callback = function defUnbindCb() { self.log.trace('disconnected'); };
this.shutdown = true;
var req = new UnbindRequest();
var finished = 0;
var cbIssued = false;
function _callback(err, res) {
if (err) {
if (!cbIssued) {
cbIssued = true;
return callback(err);
}
}
if (++finished >= self.connections.length && !cbIssued) {
cbIssued = true;
return callback(null);
}
}
this.connections.forEach(function(c) {
return self._send(req, 'unbind', _callback, c);
});
};
Client.prototype._send = function(message, expect, callback, conn) {
assert.ok(message);
assert.ok(expect);
assert.ok(callback);
var self = this;
// First select a connection
// Note bind and unbind are special in that they will pass in the
// connection since they iterate over the whole pool
if (!conn) {
function nextConn() {
if (++self.currentConnection >= self.connections.length)
self.currentConnection = 0;
return self.connections[self.currentConnection];
}
var save = this.currentConnection;
while ((conn = nextConn()) && save !== this.currentConnection);
if (!conn) {
self.emit('error', new DisconnectedError('No connections available'));
return;
}
}
assert.ok(conn);
// Now set up the callback in the messages table
message.messageID = conn.ldap.nextMessageID;
conn.ldap.messages[message.messageID] = function(res) {
if (self.log.isDebugEnabled())
self.log.debug('%s: response received: %j', conn.ldap.id, res.json);
var err = null;
if (res instanceof LDAPResult) {
delete conn.ldap.messages[message.messageID];
if (expect.indexOf(res.status) === -1) {
err = errors.getError(res);
if (typeof(callback) === 'function')
return callback(err);
return callback.emit('error', err);
}
if (typeof(callback) === 'function')
return callback(null, res);
callback.emit('end', res);
} else if (res instanceof SearchEntry) {
assert.ok(callback instanceof EventEmitter);
callback.emit('searchEntry', res);
} else {
delete conn.ldap.messages[message.messageID];
err = new errors.ProtocolError(res.type);
if (typeof(callback) === 'function')
return callback(err);
callback.emit('error', err);
}
};
// Finally send some data
if (this.log.isDebugEnabled())
this.log.debug('%s: sending request: %j', conn.ldap.id, message.json);
// Note if this was an unbind, we just go ahead and end, since there
// will never be a response
return conn.write(message.toBer(), (expect === 'unbind' ? function() {
conn.on('end', function() {
self.emit('unbind');
return callback();
});
conn.end();
} : null));
};

74
lib/control.js Normal file
View File

@ -0,0 +1,74 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var Protocol = require('./protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API
function Control(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');
if (options.criticality !== undefined &&
typeof(options.criticality) !== 'boolean')
throw new TypeError('options.criticality must be a boolean');
if (options.value && typeof(options.value) !== 'string')
throw new TypeError('options.value must be a string');
} else {
options = {};
}
this.type = options.type || '';
this.criticality = options.criticality || false;
this.value = options.value || undefined;
var self = this;
this.__defineGetter__('json', function() {
return {
controlType: self.type,
criticality: self.criticality,
controlValue: self.value
};
});
}
module.exports = Control;
Control.prototype.toString = function() {
return this.json;
};
Control.prototype.parse = function(ber) {
assert.ok(ber);
if (ber.readSequence() === null)
return false;
var end = ber.offset + ber.length;
if (ber.length) {
this.type = ber.readString();
if (ber.offset < end)
this.criticality = ber.readBoolean();
if (ber.offset < end)
this.value = ber.readString();
}
return true;
};

244
lib/dn.js Normal file
View File

@ -0,0 +1,244 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
function invalidDN(name) {
var e = new Error();
e.name = 'InvalidDistinguishedNameError';
e.message = name;
return e;
}
function isAlphaNumeric(c) {
var re = /[A-Za-z0-9]/;
return re.test(c);
}
function isWhitespace(c) {
var re = /\s/;
return re.test(c);
}
function RDN() {}
RDN.prototype.toString = function() {
var self = this;
var str = '';
Object.keys(this).forEach(function(k) {
if (str.length)
str += '+';
str += k + '=' + self[k];
});
return str;
};
function parse(name) {
if (typeof(name) !== 'string')
throw new TypeError('name (string) required');
var cur = 0;
var len = name.length;
function parseRdn() {
var rdn = new RDN();
while (cur < len) {
trim();
var attr = parseAttrType();
trim();
if (cur >= len || name[cur++] !== '=')
throw invalidDN(name);
trim();
var value = parseAttrValue();
trim();
rdn[attr] = value;
if (cur >= len || name[cur] !== '+')
break;
++cur;
}
return rdn;
}
function trim() {
while ((cur < len) && isWhitespace(name[cur]))
++cur;
}
function parseAttrType() {
var beg = cur;
while (cur < len) {
var 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() {
if (cur < len && name[cur] == '#') {
return parseBinaryAttrValue();
} else if (cur < len && name[cur] == '"') {
return parseQuotedAttrValue();
} else {
return parseStringAttrValue();
}
}
function parseBinaryAttrValue() {
var beg = cur++;
while (cur < len && isAlphaNumeric(name[cur]))
++cur;
return name.slice(beg, cur);
}
function parseQuotedAttrValue() {
var beg = cur++;
while ((cur < len) && name[cur] != '"') {
if (name[cur] === '\\')
++cur; // consume backslash, then what follows
++cur;
}
if (cur++ >= len) // no closing quote
throw invalidDN(name);
return name.slice(beg, cur);
}
function parseStringAttrValue() {
var beg = cur;
var esc = -1;
while ((cur < len) && !atTerminator()) {
if (name[cur] === '\\') {
++cur; // consume backslash, then what follows
esc = cur;
}
++cur;
}
if (cur > len) // backslash followed by nothing
throw invalidDN(name);
// Trim off (unescaped) trailing whitespace.
var end;
for (end = cur; end > beg; end--) {
if (!isWhitespace(name[end - 1]) || (esc === (end - 1)))
break;
}
return name.slice(beg, end);
}
function atTerminator() {
return (cur < len &&
(name[cur] === ',' ||
name[cur] === ';' ||
name[cur] === '+'));
}
var rdns = [];
rdns.push(parseRdn());
while (cur < len) {
if (name[cur] === ',' || name[cur] === ';') {
++cur;
rdns.push(parseRdn());
} else {
throw invalidDN(name);
}
}
return new DN(rdns);
}
///--- API
function DN(rdns) {
if (!Array.isArray(rdns))
throw new TypeError('rdns ([object]) required');
rdns.forEach(function(rdn) {
if (typeof(rdn) !== 'object')
throw new TypeError('rdns ([object]) required');
});
this.rdns = rdns.slice();
this.__defineGetter__('length', function() {
return this.rdns.length;
});
}
DN.prototype.toString = function() {
var _dn = [];
this.rdns.forEach(function(rdn) {
_dn.push(rdn.toString());
});
return _dn.join(', ');
};
DN.prototype.childOf = function(dn) {
if (!(dn instanceof DN))
dn = parse(dn);
if (this.rdns.length < dn.rdns.length)
return false;
var diff = this.rdns.length - dn.rdns.length;
for (var i = dn.rdns.length - 1; i >= 0; i--) {
var rdn = dn.rdns[i];
for (var k in rdn) {
if (rdn.hasOwnProperty(k)) {
var ourRdn = this.rdns[i + diff];
if (ourRdn[k] !== rdn[k])
return false;
}
}
}
return true;
};
DN.prototype.parentOf = function(dn) {
if (!(dn instanceof DN))
dn = parse(dn);
var parent = DN.prototype.childOf.call(dn, this);
return parent;
};
module.exports = {
parse: parse,
DN: DN,
RDN: RDN
};

140
lib/errors/index.js Normal file
View File

@ -0,0 +1,140 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var util = require('util');
var LDAPResult = require('../messages').LDAPResult;
///--- Globals
var CODES = {
LDAP_SUCCESS: 0,
LDAP_OPERATIONS_ERROR: 1,
LDAP_PROTOCOL_ERROR: 2,
LDAP_TIME_LIMIT_EXCEEDED: 3,
LDAP_SIZE_LIMIT_EXCEEDED: 4,
LDAP_COMPARE_FALSE: 5,
LDAP_COMPARE_TRUE: 6,
LDAP_AUTH_METHOD_NOT_SUPPORTED: 7,
LDAP_STRONG_AUTH_REQUIRED: 8,
LDAP_REFERRAL: 10,
LDAP_ADMIN_LIMIT_EXCEEDED: 11,
LDAP_UNAVAILABLE_CRITICAL_EXTENSION: 12,
LDAP_CONFIDENTIALITY_REQUIRED: 13,
LDAP_SASL_BIND_IN_PROGRESS: 14,
LDAP_NO_SUCH_ATTRIBUTE: 16,
LDAP_UNDEFINED_ATTRIBUTE_TYPE: 17,
LDAP_INAPPROPRIATE_MATCHING: 18,
LDAP_CONSTRAINT_VIOLATION: 19,
LDAP_ATTRIBUTE_OR_VALUE_EXISTS: 20,
LDAP_INVALID_ATTRIUBTE_SYNTAX: 21,
LDAP_NO_SUCH_OBJECT: 32,
LDAP_ALIAS_PROBLEM: 33,
LDAP_INVALID_DN_SYNTAX: 34,
LDAP_ALIAS_DEREF_PROBLEM: 36,
LDAP_INAPPROPRIATE_AUTHENTICATION: 48,
LDAP_INVALID_CREDENTIALS: 49,
LDAP_INSUFFICIENT_ACCESS_RIGHTS: 50,
LDAP_BUSY: 51,
LDAP_UNAVAILABLE: 52,
LDAP_UNWILLING_TO_PERFORM: 53,
LDAP_LOOP_DETECT: 54,
LDAP_NAMING_VIOLATION: 64,
LDAP_OBJECTCLASS_VIOLATION: 65,
LDAP_NOT_ALLOWED_ON_NON_LEAF: 66,
LDAP_NOT_ALLOWED_ON_RDN: 67,
LDAP_ENTRY_ALREADY_EXISTS: 68,
LDAP_OBJECTCLASS_MODS_PROHIBITED: 69,
LDAP_AFFECTS_MULTIPLE_DSAS: 71,
LDAP_OTHER: 80
};
var ERRORS = [];
///--- Error Base class
function LDAPError(errorName, errorCode, msg, dn, caller) {
if (Error.captureStackTrace)
Error.captureStackTrace(this, caller || LDAPError);
this.__defineGetter__('dn', function() {
return (dn ? (dn.toString() || '') : '');
});
this.__defineGetter__('code', function() {
return errorCode;
});
this.__defineGetter__('name', function() {
return errorName;
});
this.__defineGetter__('message', function() {
return msg || errorName;
});
}
util.inherits(LDAPError, Error);
///--- Exported API
// Some whacky games here to make sure all the codes are exported
module.exports = {};
Object.keys(CODES).forEach(function(code) {
module.exports[code] = CODES[code];
if (code === 'LDAP_SUCCESS')
return;
var err = '';
var msg = '';
var pieces = code.split('_').slice(1);
for (var i = 0; i < pieces.length; i++) {
var lc = pieces[i].toLowerCase();
var key = lc.charAt(0).toUpperCase() + lc.slice(1);
err += key;
msg += key + ((i + 1) < pieces.length ? ' ' : '');
}
if (!/\w+Error$/.test(err))
err += 'Error';
// At this point LDAP_OPERATIONS_ERROR is now OperationsError in $err
// and 'Operations Error' in $msg
module.exports[err] = function(message, dn, caller) {
LDAPError.call(this,
err,
CODES[code],
message || msg,
dn || null,
caller || module.exports[err]);
}
module.exports[err].constructor = module.exports[err];
util.inherits(module.exports[err], LDAPError);
ERRORS[CODES[code]] = {
err: err,
message: msg
};
});
module.exports.getError = function(res) {
if (!(res instanceof LDAPResult))
throw new TypeError('res (LDAPResult) required');
var errObj = ERRORS[res.status];
var E = module.exports[errObj.err];
return new E(res.errorMessage || errObj.message,
res.matchedDN || null,
module.exports.getError);
};
module.exports.getMessage = function(code) {
if (typeof(code) !== 'number')
throw new TypeError('code (number) required');
var errObj = ERRORS[res.status];
return (errObj && errObj.message ? errObj.message : '');
};

82
lib/filters/and_filter.js Normal file
View File

@ -0,0 +1,82 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function AndFilter(options) {
if (typeof(options) === 'object') {
if (!options.filters || !Array.isArray(options.filters))
throw new TypeError('options.filters ([Filter]) required');
this.filters = options.filters.slice();
} else {
options = {};
}
options.type = Protocol.FILTER_AND;
Filter.call(this, options);
if (!this.filters)
this.filters = [];
var self = this;
this.__defineGetter__('json', function() {
return {
type: 'And',
filters: self.filters || []
};
});
}
util.inherits(AndFilter, Filter);
module.exports = AndFilter;
AndFilter.prototype.toString = function() {
var str = '(&';
this.filters.forEach(function(f) {
str += f.toString();
});
str += ')';
return str;
};
AndFilter.prototype.matches = function(target) {
if (typeof(target) !== 'object')
throw new TypeError('target (object) required');
var matches = this.filters.length ? true : false;
for (var i = 0; i < this.filters.length; i++)
if (!this.filters[i].matches(target))
return false;
return matches;
};
AndFilter.prototype.addFilter = function(filter) {
if (!filter || typeof(filter) !== 'object')
throw new TypeError('filter (object) required');
this.filters.push(filter);
};
AndFilter.prototype._toBer = function(ber) {
assert.ok(ber);
this.filters.forEach(function(f) {
ber = f.toBer(ber);
});
return ber;
};

View File

@ -0,0 +1,75 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function ApproximateFilter(options) {
if (typeof(options) === 'object') {
if (!options.attribute || typeof(options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
if (!options.value || typeof(options.value) !== 'string')
throw new TypeError('options.value (string) required');
this.attribute = options.attribute;
this.value = options.value;
} else {
options = {};
}
options.type = Protocol.FILTER_APPROX;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function() {
return {
type: 'ApproximateMatch',
attribute: self.attribute || undefined,
value: self.value || undefined
};
});
}
util.inherits(ApproximateFilter, Filter);
module.exports = ApproximateFilter;
ApproximateFilter.prototype.toString = function() {
return '(' + this.attribute + '~=' + this.value + ')';
};
ApproximateFilter.prototype.matches = function(target) {
if (typeof(target) !== 'object')
throw new TypeError('target (object) required');
var matches = false;
if (target.hasOwnProperty(this.attribute))
matches = (this.value === target[this.attribute]);
return matches;
};
ApproximateFilter.prototype.parse = function(ber) {
assert.ok(ber);
this.attribute = ber.readString();
this.value = ber.readString();
return true;
};
ApproximateFilter.prototype._toBer = function(ber) {
assert.ok(ber);
ber.writeString(this.attribute);
ber.writeString(this.value);
return ber;
};

View File

@ -0,0 +1,75 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function EqualityFilter(options) {
if (typeof(options) === 'object') {
if (!options.attribute || typeof(options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
if (!options.value || typeof(options.value) !== 'string')
throw new TypeError('options.value (string) required');
this.attribute = options.attribute;
this.value = options.value;
} else {
options = {};
}
options.type = Protocol.FILTER_EQUALITY;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function() {
return {
type: 'EqualityMatch',
attribute: self.attribute || undefined,
value: self.value || undefined
};
});
}
util.inherits(EqualityFilter, Filter);
module.exports = EqualityFilter;
EqualityFilter.prototype.toString = function() {
return '(' + this.attribute + '=' + this.value + ')';
};
EqualityFilter.prototype.matches = function(target) {
if (typeof(target) !== 'object')
throw new TypeError('target (object) required');
var matches = false;
if (target.hasOwnProperty(this.attribute))
matches = (this.value === target[this.attribute]);
return matches;
};
EqualityFilter.prototype.parse = function(ber) {
assert.ok(ber);
this.attribute = ber.readString();
this.value = ber.readString();
return true;
};
EqualityFilter.prototype._toBer = function(ber) {
assert.ok(ber);
ber.writeString(this.attribute);
ber.writeString(this.value);
return ber;
};

38
lib/filters/filter.js Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var asn1 = require('asn1');
///--- Globals
var BerWriter = asn1.BerWriter;
///--- API
function Filter(options) {
if (!options || typeof(options) !== 'object')
throw new TypeError('options (object) required');
if (typeof(options.type) !== 'number')
throw new TypeError('options.type (number) required');
this._type = options.type;
var self = this;
this.__defineGetter__('type', function() {
return '0x' + self._type.toString(16);
});
}
module.exports = Filter;
Filter.prototype.toBer = function(ber) {
if (!ber || !(ber instanceof BerWriter))
throw new TypeError('ber (BerWriter) required');
ber.startSequence(this._type);
ber = this._toBer(ber);
ber.endSequence();
return ber;
};

76
lib/filters/ge_filter.js Normal file
View File

@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function GreaterThanEqualsFilter(options) {
if (typeof(options) === 'object') {
if (!options.attribute || typeof(options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
if (!options.value || typeof(options.value) !== 'string')
throw new TypeError('options.value (string) required');
this.attribute = options.attribute;
this.value = options.value;
} else {
options = {};
}
options.type = Protocol.FILTER_GE;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function() {
return {
type: 'GreaterThanEqualsMatch',
attribute: self.attribute || undefined,
value: self.value || undefined
};
});
}
util.inherits(GreaterThanEqualsFilter, Filter);
module.exports = GreaterThanEqualsFilter;
GreaterThanEqualsFilter.prototype.toString = function() {
return '(' + this.attribute + '>=' + this.value + ')';
};
GreaterThanEqualsFilter.prototype.matches = function(target) {
if (typeof(target) !== 'object')
throw new TypeError('target (object) required');
var matches = false;
if (target.hasOwnProperty(this.attribute))
matches = (target[this.attribute] >= this.value);
return matches;
};
GreaterThanEqualsFilter.prototype.parse = function(ber) {
assert.ok(ber);
this.attribute = ber.readString();
this.value = ber.readString();
return true;
};
GreaterThanEqualsFilter.prototype._toBer = function(ber) {
assert.ok(ber);
ber.writeString(this.attribute);
ber.writeString(this.value);
return ber;
};

320
lib/filters/index.js Normal file
View File

@ -0,0 +1,320 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var asn1 = require('asn1');
var Protocol = require('../protocol');
var Filter = require('./filter');
var AndFilter = require('./and_filter');
var ApproximateFilter = require('./approx_filter');
var EqualityFilter = require('./equality_filter');
var GreaterThanEqualsFilter = require('./ge_filter');
var LessThanEqualsFilter = require('./le_filter');
var NotFilter = require('./not_filter');
var OrFilter = require('./or_filter');
var PresenceFilter = require('./presence_filter');
var SubstringFilter = require('./substr_filter');
///--- Globals
var BerReader = asn1.BerReader;
///--- Internal Parsers
/*
* This is a pretty naive approach to parsing, but it's relatively short amount
* of code. Basically, we just build a stack as we go.
*/
function _filterStringToStack(str) {
assert.ok(str);
var tmp = '';
var esc = false;
var stack = [];
var depth = -1;
var open = false;
for (var i = 0; i < str.length; i++) {
var c = str[i];
if (esc) {
esc = false;
tmp += c;
continue;
}
switch (c) {
case '(':
open = true;
tmp = '';
stack[++depth] = '';
break;
case ')':
if (open) {
stack[depth].value = tmp;
tmp = '';
}
open = false;
break;
case '&':
case '|':
case '!':
stack[depth] = c;
break;
case '=':
stack[depth] = { attribute: tmp, op: c };
tmp = '';
break;
case '>':
case '<':
case '~':
if (!(str[++i] === '='))
throw new Error('Invalid filter: ' + tmp + c + str[i]);
stack[depth] = {attribute: tmp, op: c};
tmp = '';
break;
case '\\':
esc = true;
default:
tmp += c;
break;
}
}
if (open)
throw new Error('Invalid filter: ' + str);
return stack;
}
function _parseString(str) {
assert.ok(str);
var stack = _filterStringToStack(str);
if (!stack || !stack.length)
throw new Error('Invalid filter: ' + str);
debugger;
var f;
var filters = [];
for (var i = stack.length - 1; i >= 0; i--) {
if (stack[i] === '&') {
filters.unshift(new AndFilter({
filters: filters
}));
filters.length = 1;
} else if (stack[i] === '|') {
filters.unshift(new OrFilter({
filters: filters
}));
filters.length = 1;
} else if (stack[i] === '!') {
filters.push(new NotFilter({
filter: filters.pop()
}));
} else {
switch (stack[i].op) {
case '=': // could be presence, equality or substr
if (stack[i].value === '*') {
filters.push(new PresenceFilter(stack[i]));
} else {
var vals = [''];
var ndx = 0;
var esc = false;
for (var j = 0; j < stack[i].value.length; j++) {
var c = stack[i].value[j];
if (c === '\\') {
if (esc) {
esc = true;
} else {
vals[ndx] += c;
esc = false;
}
} else if (c === '*') {
if (esc) {
vals[ndx] = c;
} else {
vals[++ndx] = '';
}
} else {
vals[ndx] += c;
}
}
if (vals.length === 1) {
filters.push(new EqualityFilter(stack[i]));
} else {
filters.push(new SubstringFilter({
attribute: stack[i].attribute,
initial: vals.shift(),
'final': vals.pop(),
any: vals
}));
}
}
break;
case '~':
filters.push(new ApproximateFilter(stack[i]));
break;
case '>':
filters.push(new GreaterThanEqualsFilter(stack[i]));
break;
case '<':
filters.push(new LessThanEqualsFilter(stack[i]));
break;
default:
throw new Error('Invalid filter (op=' + stack[i].op + '): ' + str);
}
}
}
if (filters.length !== 1)
throw new Error('Invalid filter: ' + str);
return filters.pop();
}
/*
* A filter looks like this coming in:
* Filter ::= CHOICE {
* and [0] SET OF Filter,
* or [1] SET OF Filter,
* not [2] Filter,
* equalityMatch [3] AttributeValueAssertion,
* substrings [4] SubstringFilter,
* greaterOrEqual [5] AttributeValueAssertion,
* lessOrEqual [6] AttributeValueAssertion,
* present [7] AttributeType,
* approxMatch [8] AttributeValueAssertion,
* extensibleMatch [9] MatchingRuleAssertion --v3 only
* }
*
* SubstringFilter ::= SEQUENCE {
* type AttributeType,
* SEQUENCE OF CHOICE {
* initial [0] IA5String,
* any [1] IA5String,
* final [2] IA5String
* }
* }
*
* The extensibleMatch was added in LDAPv3:
*
* MatchingRuleAssertion ::= SEQUENCE {
* matchingRule [1] MatchingRuleID OPTIONAL,
* type [2] AttributeDescription OPTIONAL,
* matchValue [3] AssertionValue,
* dnAttributes [4] BOOLEAN DEFAULT FALSE
* }
*/
function _parse(ber) {
assert.ok(ber);
function parseSet(f) {
var end = ber.offset + ber.length;
while (ber.offset < end)
f.addFilter(_parse(ber));
}
var f;
var type = ber.readSequence();
switch (type) {
case Protocol.FILTER_AND:
f = new AndFilter();
parseSet(f);
break;
case Protocol.FILTER_APPROX:
f = new ApproximateFilter();
f.parse(ber);
break;
case Protocol.FILTER_EQUALITY:
f = new EqualityFilter();
f.parse(ber);
return f;
case Protocol.FILTER_GE:
f = new GreaterThanEqualsFilter();
f.parse(ber);
return f;
case Protocol.FILTER_LE:
f = new LessThanEqualsFilter();
f.parse(ber);
return f;
case Protocol.FILTER_NOT:
var _f = _parse(ber);
f = new NotFilter({
filter: _f
});
break;
case Protocol.FILTER_OR:
f = new OrFilter();
parseSet(f);
break;
case Protocol.FILTER_PRESENT:
f = new PresenceFilter();
f.parse(ber);
break;
case Protocol.FILTER_SUBSTRINGS:
f = new SubstringFilter();
f.parse(ber);
break;
default:
throw new Error('Invalid search filter type: 0x' + type.toString(16));
}
assert.ok(f);
return f;
}
///--- API
module.exports = {
parse: function(ber) {
if (!ber || !(ber instanceof BerReader))
throw new TypeError('ber (BerReader) required');
return _parse(ber);
},
parseString: function(filter) {
if (!filter || typeof(filter) !== 'string')
throw new TypeError('filter (string) required');
return _parseString(filter);
},
AndFilter: AndFilter,
ApproximateFilter: ApproximateFilter,
EqualityFilter: EqualityFilter,
GreaterThanEqualsFilter: GreaterThanEqualsFilter,
LessThanEqualsFilter: LessThanEqualsFilter,
NotFilter: NotFilter,
OrFilter: OrFilter,
PresenceFilter: PresenceFilter,
SubstringFilter: SubstringFilter,
Filter: Filter
};

76
lib/filters/le_filter.js Normal file
View File

@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function LessThanEqualsFilter(options) {
if (typeof(options) === 'object') {
if (!options.attribute || typeof(options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
if (!options.value || typeof(options.value) !== 'string')
throw new TypeError('options.value (string) required');
this.attribute = options.attribute;
this.value = options.value;
} else {
options = {};
}
options.type = Protocol.FILTER_LE;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function() {
return {
type: 'LessThanEqualsMatch',
attribute: self.attribute || undefined,
value: self.value || undefined
};
});
}
util.inherits(LessThanEqualsFilter, Filter);
module.exports = LessThanEqualsFilter;
LessThanEqualsFilter.prototype.toString = function() {
return '(' + this.attribute + '<=' + this.value + ')';
};
LessThanEqualsFilter.prototype.matches = function(target) {
if (typeof(target) !== 'object')
throw new TypeError('target (object) required');
var matches = false;
if (target.hasOwnProperty(this.attribute))
matches = (target[this.attribute] <= this.value);
return matches;
};
LessThanEqualsFilter.prototype.parse = function(ber) {
assert.ok(ber);
this.attribute = ber.readString();
this.value = ber.readString();
return true;
};
LessThanEqualsFilter.prototype._toBer = function(ber) {
assert.ok(ber);
ber.writeString(this.attribute);
ber.writeString(this.value);
return ber;
};

51
lib/filters/not_filter.js Normal file
View File

@ -0,0 +1,51 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function NotFilter(options) {
if (typeof(options) !== 'object')
throw new TypeError('options (object) required');
if (!options.filter || !(options.filter instanceof Filter))
throw new TypeError('options.filter (Filter) required');
options.type = Protocol.FILTER_NOT;
Filter.call(this, options);
this.filter = options.filter;
var self = this;
this.__defineGetter__('json', function() {
return {
type: 'Not',
filter: self.filter
};
});
}
util.inherits(NotFilter, Filter);
module.exports = NotFilter;
NotFilter.prototype.toString = function() {
return '(!' + this.filter.toString() + ')';
};
NotFilter.prototype.matches = function(target) {
return !this.filter.matches(target);
};
NotFilter.prototype._toBer = function(ber) {
assert.ok(ber);
return this.filter.toBer(ber);
};

80
lib/filters/or_filter.js Normal file
View File

@ -0,0 +1,80 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function OrFilter(options) {
if (typeof(options) === 'object') {
if (!options.filters || !Array.isArray(options.filters))
throw new TypeError('options.filters ([Filter]) required');
this.filters = options.filters.slice();
} else {
options = {};
}
options.type = Protocol.FILTER_OR;
Filter.call(this, options);
if (!this.filters)
this.filters = [];
var self = this;
this.__defineGetter__('json', function() {
return {
type: 'Or',
filters: self.filters || []
};
});
}
util.inherits(OrFilter, Filter);
module.exports = OrFilter;
OrFilter.prototype.toString = function() {
var str = '(|';
this.filters.forEach(function(f) {
str += f.toString();
});
str += ')';
return str;
};
OrFilter.prototype.matches = function(target) {
if (typeof(target) !== 'object')
throw new TypeError('target (object) required');
for (var i = 0; i < this.filters.length; i++)
if (this.filters[i].matches(target))
return true;
return false;
};
OrFilter.prototype.addFilter = function(filter) {
if (!filter || typeof(filter) !== 'object')
throw new TypeError('filter (object) required');
this.filters.push(filter);
};
OrFilter.prototype._toBer = function(ber) {
assert.ok(ber);
this.filters.forEach(function(f) {
ber = f.toBer(ber);
});
return ber;
};

View File

@ -0,0 +1,67 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function PresenceFilter(options) {
if (typeof(options) === 'object') {
if (!options.attribute || typeof(options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
this.attribute = options.attribute;
} else {
options = {};
}
options.type = Protocol.FILTER_PRESENT;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function() {
return {
type: 'PresenceMatch',
attribute: self.attribute || undefined
};
});
}
util.inherits(PresenceFilter, Filter);
module.exports = PresenceFilter;
PresenceFilter.prototype.toString = function() {
return '(' + this.attribute + '=*)';
};
PresenceFilter.prototype.matches = function(target) {
if (typeof(target) !== 'object')
throw new TypeError('target (object) required');
var matches = false;
if (target.hasOwnProperty(this.attribute))
matches = true;
return matches;
};
PresenceFilter.prototype.parse = function(ber) {
assert.ok(ber);
this.attribute = ber.readString();
return true;
};
PresenceFilter.prototype._toBer = function(ber) {
assert.ok(ber);
ber.writeString(this.attribute);
return ber;
};

View File

@ -0,0 +1,133 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var Filter = require('./filter');
var Protocol = require('../protocol');
///--- API
function SubstringFilter(options) {
if (typeof(options) === 'object') {
if (!options.attribute || typeof(options.attribute) !== 'string')
throw new TypeError('options.attribute (string) required');
this.attribute = options.attribute;
this.initial = options.initial;
this.any = options.any ? options.any.slice(0) : [];
this['final'] = options['final'];
} else {
options = {};
}
if (!this.any)
this.any = [];
options.type = Protocol.FILTER_SUBSTRINGS;
Filter.call(this, options);
var self = this;
this.__defineGetter__('json', function() {
return {
type: 'SubstringMatch',
initial: self.initial || undefined,
any: self.any || undefined,
'final': self['final'] || undefined
};
});
}
util.inherits(SubstringFilter, Filter);
module.exports = SubstringFilter;
SubstringFilter.prototype.toString = function() {
var str = '(' + this.attribute + '=';
if (this.initial)
str += this.initial + '*';
this.any.forEach(function(s) {
str += s + '*';
});
if (this['final'])
str += this['final'];
str += ')';
return str;
};
SubstringFilter.prototype.matches = function(target) {
if (typeof(target) !== 'object')
throw new TypeError('target (object) required');
if (target.hasOwnProperty(this.attribute)) {
var re = '';
if (this.initial)
re += '^' + this.initial + '.*';
this.any.forEach(function(s) {
re += s + '.*';
});
if (this['final'])
re += this['final'] + '$';
var matcher = new RegExp(re);
return matcher.test(target[this.attribute]);
}
return true;
};
SubstringFilter.prototype.parse = function(ber) {
assert.ok(ber);
this.attribute = ber.readString();
ber.readSequence();
var end = ber.offset + ber.length;
while (ber.offset < end) {
var tag = ber.peek();
switch (tag) {
case 0x80: // Initial
this.initial = ber.readString(tag);
break;
case 0x81: // Any
this.any.push(ber.readString(tag));
break;
case 0x82: // Final
this['final'] = ber.readString(tag);
break;
default:
throw new Error('Invalid substrings filter type: 0x' + tag.toString(16));
}
}
return true;
};
SubstringFilter.prototype._toBer = function(ber) {
assert.ok(ber);
ber.writeString(this.attribute);
ber.startSequence();
if (this.initial)
ber.writeString(this.initial, 0x80);
if (this.any && this.any.length)
this.any.forEach(function(s) {
ber.writeString(s, 0x81);
});
if (this['final'])
ber.writeString(this['final'], 0x82);
ber.endSequence();
return ber;
};

93
lib/index.js Normal file
View File

@ -0,0 +1,93 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var Client = require('./client');
var dn = require('./dn');
var errors = require('./errors');
var filters = require('./filters');
var messages = require('./messages');
var server = require('./server');
var logStub = require('./log_stub');
var url = require('./url');
var Attribute = require('./attribute');
var Change = require('./change');
var Control = require('./control');
var Protocol = require('./protocol');
/// Hack a few things we need (i.e., "monkey patch" the prototype)
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(str) {
var re = new RegExp('^' + str);
return re.test(this);
};
}
if (!String.prototype.endsWith) {
String.prototype.endsWith = function(str) {
var re = new RegExp(str + '$');
return re.test(this);
};
}
///--- API
module.exports = {
Client: Client,
createClient: function(options) {
if (typeof(options) !== 'object')
throw new TypeError('options (object) required');
return new Client(options);
},
createServer: server.createServer,
dn: dn,
DN: dn.DN,
DN: dn.RDN,
parseDN: dn.parse,
filters: filters,
parseFilter: filters.parseString,
Attribute: Attribute,
Change: Change,
Control: Control,
log4js: logStub,
url: url
};
///--- Export all the childrenz
var k;
for (k in Protocol) {
if (Protocol.hasOwnProperty(k))
module.exports[k] = Protocol[k];
}
for (k in messages) {
if (messages.hasOwnProperty(k))
module.exports[k] = messages[k];
}
for (k in filters) {
if (filters.hasOwnProperty(k)) {
if (k !== 'parse' && k !== 'parseString')
module.exports[k] = filters[k];
}
}
for (k in errors) {
if (errors.hasOwnProperty(k)) {
module.exports[k] = errors[k];
}
}

146
lib/log_stub.js Normal file
View File

@ -0,0 +1,146 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
///--- Globals
var FMT_STR = '%d-%s-%s %s:%s:%sZ %s - %s: ';
var _i = 0;
var LEVELS = {
Trace: _i++,
Debug: _i++,
Info: _i++,
Warn: _i++,
Error: _i++,
Fatal: _i++
};
var level = 'Info';
// --- Helpers
function pad(val) {
if (parseInt(val, 10) < 10) {
val = '0' + val;
}
return val;
}
function format(level, name, args) {
var d = new Date();
var fmtStr = args.shift();
var fmtArgs = [
d.getUTCFullYear(),
pad(d.getUTCMonth()),
pad(d.getUTCDate()),
pad(d.getUTCHours()),
pad(d.getUTCMinutes()),
pad(d.getUTCSeconds()),
level,
name
];
args = fmtArgs.concat(args);
var output = (FMT_STR + fmtStr).replace(/%[sdj]/g, function(match) {
switch (match) {
case '%s': return new String(args.shift());
case '%d': return new Number(args.shift());
case '%j': return JSON.stringify(args.shift());
default:
return match;
}
});
return output;
}
///--- API
function Log(name) {
this.name = name;
}
Log.prototype._write = function(level, args) {
var data = format(level, this.name, args);
console.error(data);
};
Log.prototype.isTraceEnabled = function() {
return (LEVELS.Trace >= LEVELS[level]);
};
Log.prototype.trace = function() {
if (this.isTraceEnabled())
this._write('TRACE', Array.prototype.slice.call(arguments));
};
Log.prototype.isDebugEnabled = function() {
return (LEVELS.Debug >= LEVELS[level]);
};
Log.prototype.debug = function() {
if (this.isDebugEnabled())
this._write('DEBUG', Array.prototype.slice.call(arguments));
};
Log.prototype.isInfoEnabled = function() {
return (LEVELS.Info >= LEVELS[level]);
};
Log.prototype.info = function() {
if (this.isInfoEnabled())
this._write('INFO', Array.prototype.slice.call(arguments));
};
Log.prototype.isWarnEnabled = function() {
return (LEVELS.Warn >= LEVELS[level]);
};
Log.prototype.warn = function() {
if (this.isWarnEnabled())
this._write('WARN', Array.prototype.slice.call(arguments));
};
Log.prototype.isErrorEnabled = function() {
return (LEVELS.Error >= LEVELS[level]);
};
Log.prototype.error = function() {
if (this.isErrorEnabled())
this._write('ERROR', Array.prototype.slice.call(arguments));
};
Log.prototype.isFatalEnabled = function() {
return (LEVELS.Fatal >= LEVELS[level]);
};
Log.prototype.fatal = function() {
if (this.isFatalEnabled())
this._write('FATAL', Array.prototype.slice.call(arguments));
};
module.exports = {
setLevel: function(l) {
if (LEVELS[l] !== undefined)
level = l;
return level;
},
getLogger: function(name) {
if (!name || typeof(name) !== 'string')
throw new TypeError('name (string) required');
return new Log(name);
}
};

View File

@ -0,0 +1,98 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Attribute = require('../attribute');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API
function AddRequest(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.entry && !(options.entry instanceof dn.DN))
throw new TypeError('options.entry must be a DN');
if (options.attributes) {
if (!Array.isArray(options.attributes))
throw new TypeError('options.attributes must be [Attribute]');
options.attributes.forEach(function(a) {
if (!Attribute.isAttribute(a))
throw new TypeError('options.attributes must be [Attribute]');
});
}
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REQ_ADD;
LDAPMessage.call(this, options);
this.entry = options.entry || null;
this.attributes = options.attributes ? options.attributes.slice(0) : [];
var self = this;
this.__defineGetter__('type', function() { return 'AddRequest'; });
this.__defineGetter__('_dn', function() { return self.entry; });
}
util.inherits(AddRequest, LDAPMessage);
module.exports = AddRequest;
AddRequest.prototype._parse = function(ber) {
assert.ok(ber);
this.entry = dn.parse(ber.readString());
ber.readSequence();
var end = ber.offset + ber.length;
while (ber.offset < end) {
var a = new Attribute();
a.parse(ber);
this.attributes.push(a);
}
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;
};

View File

@ -0,0 +1,23 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function AddResponse(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REP_ADD;
LDAPResult.call(this, options);
}
util.inherits(AddResponse, LDAPResult);
module.exports = AddResponse;

View File

@ -0,0 +1,93 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
var LDAP_BIND_SIMPLE = 'Simple';
var LDAP_BIND_SASL = 'Sasl';
///--- API
function BindRequest(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.name && !(options.name instanceof dn.DN))
throw new TypeError('options.entry must be a DN');
} else {
options = {};
}
options.protocolOp = Protocol.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 || '';
var self = this;
this.__defineGetter__('type', function() { return 'BindRequest'; });
this.__defineGetter__('_dn', function() { return self.name.toString(); });
}
util.inherits(BindRequest, LDAPMessage);
module.exports = BindRequest;
BindRequest.prototype._parse = function(ber) {
assert.ok(ber);
this.version = ber.readInt();
this.name = dn.parse(ber.readString());
var 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;
};

View File

@ -0,0 +1,23 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function BindResponse(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REP_BIND;
LDAPResult.call(this, options);
}
util.inherits(BindResponse, LDAPResult);
module.exports = BindResponse;

View File

@ -0,0 +1,82 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Attribute = require('../attribute');
var Protocol = require('../protocol');
///--- API
function CompareRequest(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.entry && !(options.entry instanceof dn.DN))
throw new TypeError('options.entry must be a DN');
if (options.attribute && typeof(options.attribute) !== 'string')
throw new TypeError('options.attribute must be a string');
if (options.value && typeof(options.value) !== 'string')
throw new TypeError('options.value must be a string');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REQ_COMPARE;
LDAPMessage.call(this, options);
this.entry = options.entry || null;
this.attribute = options.attribute || '';
this.value = options.value || '';
var self = this;
this.__defineGetter__('type', function() { return 'CompareRequest'; });
this.__defineGetter__('_dn', function() {
return self.entry ? self.entry.toString() : '';
});
}
util.inherits(CompareRequest, LDAPMessage);
module.exports = CompareRequest;
CompareRequest.prototype._parse = function(ber) {
assert.ok(ber);
this.entry = dn.parse(ber.readString());
ber.readSequence();
this.attribute = ber.readString();
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;
};

View File

@ -0,0 +1,23 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function CompareResponse(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REP_COMPARE;
LDAPResult.call(this, options);
}
util.inherits(CompareResponse, LDAPResult);
module.exports = CompareResponse;

View File

@ -0,0 +1,78 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Attribute = require('../attribute');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API
function DeleteRequest(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.entry && !(options.entry instanceof dn.DN))
throw new TypeError('options.entry must be a DN');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REQ_DELETE;
LDAPMessage.call(this, options);
this.entry = options.entry || null;
var self = this;
this.__defineGetter__('type', function() { return 'DeleteRequest'; });
this.__defineGetter__('_dn', function() {
return self.entry ? self.entry.toString() : '';
});
}
util.inherits(DeleteRequest, LDAPMessage);
module.exports = DeleteRequest;
DeleteRequest.prototype._parse = function(ber, length) {
assert.ok(ber);
// What a hack; LDAP is so annoying with its decisions of what to
// shortcut, so this is totally a hack to work around the way the delete
// message is structured
this.entry = dn.parse(ber.buffer.slice(0, length).toString());
ber._offset += length;
return true;
};
DeleteRequest.prototype._toBer = function(ber) {
assert.ok(ber);
var buf = new Buffer(this.entry.toString());
for (var 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;
};

View File

@ -0,0 +1,23 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function DeleteResponse(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REP_DELETE;
LDAPResult.call(this, options);
}
util.inherits(DeleteResponse, LDAPResult);
module.exports = DeleteResponse;

View File

@ -0,0 +1,97 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Attribute = require('../attribute');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API
function ExtendedRequest(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.requestName && typeof(options.requestName) !== 'string')
throw new TypeError('options.requestName must be a string');
if (options.requestValue && typeof(options.requestValue) !== 'string')
throw new TypeError('options.requestValue must be a string');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REQ_EXTENSION;
LDAPMessage.call(this, options);
this.requestName = options.requestName || '';
this.requestValue = options.requestValue || undefined;
this.__defineGetter__('type', function() { return 'ExtendedRequest'; });
this.__defineGetter__('_dn', function() { return this.requestName; });
this.__defineGetter__('name', function() {
return this.requestName;
});
this.__defineGetter__('value', function() {
return this.requestValue;
});
this.__defineSetter__('name', function(name) {
if (typeof(name) !== 'string')
throw new TypeError('name must be a string');
this.requestName = name;
});
this.__defineSetter__('value', function(val) {
if (typeof(val) !== 'string')
throw new TypeError('value must be a string');
this.requestValue = val;
});
}
util.inherits(ExtendedRequest, LDAPMessage);
module.exports = ExtendedRequest;
ExtendedRequest.prototype._parse = function(ber) {
assert.ok(ber);
this.requestName = ber.readString(0x80);
if (ber.peek() === 0x81)
this.requestValue = ber.readString(0x81);
return true;
};
ExtendedRequest.prototype._toBer = function(ber) {
assert.ok(ber);
ber.writeString(this.requestName, 0x80);
if (this.requestValue)
ber.writeString(this.requestValue, 0x81);
return ber;
};
ExtendedRequest.prototype._json = function(j) {
assert.ok(j);
j.requestName = this.requestName;
j.requestValue = this.requestValue;
return j;
};

View File

@ -0,0 +1,92 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function ExtendedResponse(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.responseName && typeof(options.responseName) !== 'string')
throw new TypeError('options.responseName must be a string');
if (options.responseValue && typeof(options.responseValue) !== 'string')
throw new TypeError('options.responseValue must be a string');
} else {
options = {};
}
this.responseName = options.responseName || undefined;
this.responseValue = options.responseValue || undefined;
options.protocolOp = Protocol.LDAP_REP_EXTENSION;
LDAPResult.call(this, options);
this.__defineGetter__('name', function() {
return this.responseName;
});
this.__defineGetter__('value', function() {
return this.responseValue;
});
this.__defineSetter__('name', function(name) {
if (typeof(name) !== 'string')
throw new TypeError('name must be a string');
this.responseName = name;
});
this.__defineSetter__('value', function(val) {
if (typeof(val) !== 'string')
throw new TypeError('value must be a string');
this.responseValue = val;
});
}
util.inherits(ExtendedResponse, LDAPResult);
module.exports = ExtendedResponse;
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;
};

57
lib/messages/index.js Normal file
View File

@ -0,0 +1,57 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var Parser = require('./parser');
var AddRequest = require('./add_request');
var AddResponse = require('./add_response');
var BindRequest = require('./bind_request');
var BindResponse = require('./bind_response');
var CompareRequest = require('./compare_request');
var CompareResponse = require('./compare_response');
var DeleteRequest = require('./del_request');
var DeleteResponse = require('./del_response');
var ExtendedRequest = require('./ext_request');
var ExtendedResponse = require('./ext_response');
var ModifyRequest = require('./modify_request');
var ModifyResponse = require('./modify_response');
var ModifyDNRequest = require('./moddn_request');
var ModifyDNResponse = require('./moddn_response');
var SearchRequest = require('./search_request');
var SearchEntry = require('./search_entry');
var SearchResponse = require('./search_response');
var UnbindRequest = require('./unbind_request');
var UnbindResponse = require('./unbind_response');
///--- API
module.exports = {
LDAPMessage: LDAPMessage,
LDAPResult: LDAPResult,
Parser: Parser,
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,
SearchResponse: SearchResponse,
UnbindRequest: UnbindRequest,
UnbindResponse: UnbindResponse
};

106
lib/messages/message.js Normal file
View File

@ -0,0 +1,106 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var Control = require('../control');
var Protocol = require('../protocol');
var logStub = require('../log_stub');
///--- Globals
var Ber = asn1.Ber;
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- API
/**
* LDAPMessage structure.
*
* @param {Object} options stuff.
*/
function LDAPMessage(options) {
if (!options || typeof(options) !== 'object')
throw new TypeError('options (object) required');
this.messageID = options.messageID || 0;
this.protocolOp = options.protocolOp || undefined;
this.controls = options.controls ? options.controls.slice(0) : [];
this.log4js = options.log4js || logStub;
var self = this;
this.__defineGetter__('id', function() { return self.messageID; });
this.__defineGetter__('dn', function() { return self._dn || ''; });
this.__defineGetter__('type', function() { return 'LDAPMessage'; });
this.__defineGetter__('json', function() {
var j = {
messageID: self.messageID,
protocolOp: self.type
};
j = self._json(j);
j.controls = self.controls;
return j;
});
this.__defineGetter__('log', function() {
if (!self._log)
self._log = self.log4js.getLogger(self.type);
return self._log;
});
}
module.exports = LDAPMessage;
LDAPMessage.prototype.toString = function() {
return JSON.stringify(this.json);
};
LDAPMessage.prototype.parse = function(data, length) {
if (!data || !Buffer.isBuffer(data))
throw new TypeError('data (buffer) required');
if (this.log.isTraceEnabled())
this.log.trace('parse: data=%s, len=%d', util.inspect(data), length);
var ber = new BerReader(data);
// Delegate off to the specific type to parse
this._parse(ber, length);
// Look for controls
if (ber.peek === Protocol.LDAP_CONTROLS && ber.offset < length) {
ber.readSequence();
var end = ber.offset + ber.length;
while (ber.offset < end) {
var c = new Control();
if (c.parse(ber))
this.controls.push(c);
}
}
if (this.log.isTraceEnabled())
this.log.trace('Parsing done: %j', this.json);
return true;
};
LDAPMessage.prototype.toBer = function() {
var writer = new BerWriter();
writer.startSequence();
writer.writeInt(this.messageID);
writer.startSequence(this.protocolOp);
if (this._toBer)
writer = this._toBer(writer);
writer.endSequence();
writer.endSequence();
return writer.buffer;
};

View File

@ -0,0 +1,95 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Attribute = require('../attribute');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API
function ModifyDNRequest(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.entry && !(options.entry instanceof dn.DN))
throw new TypeError('options.entry must be a DN');
if (options.newRdn && !(options.newRdn instanceof dn.DN))
throw new TypeError('options.newRdn must be a DN');
if (options.deleteOldRdn !== undefined &&
typeof(options.deleteOldRdn) !== 'boolean')
throw new TypeError('options.deleteOldRdn must be a boolean');
if (options.newSuperior && !(options.newSuperior instanceof dn.DN))
throw new TypeError('options.newSuperior must be a DN');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REQ_MODRDN;
LDAPMessage.call(this, options);
this.entry = options.entry || null;
this.newRdn = options.newRdn || null;
this.deleteOldRdn = options.deleteOldRdn || false;
this.newSuperior = options.newSuperior || null;
var self = this;
this.__defineGetter__('type', function() { return 'ModifyDNRequest'; });
this.__defineGetter__('_dn', function() {
return self.entry ? self.entry.toString() : '';
});
}
util.inherits(ModifyDNRequest, LDAPMessage);
module.exports = ModifyDNRequest;
ModifyDNRequest.prototype._parse = function(ber) {
assert.ok(ber);
this.entry = dn.parse(ber.readString());
this.newRdn = dn.parse(ber.readString());
this.deleteOldRdn = ber.readBoolean();
if (ber.peek() === Ber.OctetString)
this.newSuperior = ber.readString();
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)
ber.writeString(this.newSuperior.toString());
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;
};

View File

@ -0,0 +1,23 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function ModifyDNResponse(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REP_MODRDN;
LDAPResult.call(this, options);
}
util.inherits(ModifyDNResponse, LDAPResult);
module.exports = ModifyDNResponse;

View File

@ -0,0 +1,93 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var Change = require('../change');
var Protocol = require('../protocol');
///--- API
function ModifyRequest(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.object && !(options.object instanceof dn.DN))
throw new TypeError('options.object must be a DN');
if (options.attributes) {
if (!Array.isArray(options.attributes))
throw new TypeError('options.attributes must be [Attribute]');
options.attributes.forEach(function(a) {
if (!(a instanceof Attribute))
throw new TypeError('options.attributes must be [Attribute]');
});
}
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REQ_MODIFY;
LDAPMessage.call(this, options);
this.object = options.object || null;
this.changes = options.changes ? options.changes.slice(0) : [];
var self = this;
this.__defineGetter__('type', function() { return 'ModifyRequest'; });
this.__defineGetter__('_dn', function() {
return self.object ? self.object.toString() : '';
});
}
util.inherits(ModifyRequest, LDAPMessage);
module.exports = ModifyRequest;
ModifyRequest.prototype._parse = function(ber) {
assert.ok(ber);
this.object = dn.parse(ber.readString());
ber.readSequence();
var end = ber.offset + ber.length;
while (ber.offset < end) {
var c = new Change();
c.parse(ber);
this.changes.push(c);
}
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;
};

View File

@ -0,0 +1,23 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function ModifyResponse(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REP_MODIFY;
LDAPResult.call(this, options);
}
util.inherits(ModifyResponse, LDAPResult);
module.exports = ModifyResponse;

247
lib/messages/parser.js Normal file
View File

@ -0,0 +1,247 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var asn1 = require('asn1');
var AddRequest = require('./add_request');
var AddResponse = require('./add_response');
var BindRequest = require('./bind_request');
var BindResponse = require('./bind_response');
var CompareRequest = require('./compare_request');
var CompareResponse = require('./compare_response');
var DeleteRequest = require('./del_request');
var DeleteResponse = require('./del_response');
var ExtendedRequest = require('./ext_request');
var ExtendedResponse = require('./ext_response');
var ModifyRequest = require('./modify_request');
var ModifyResponse = require('./modify_response');
var ModifyDNRequest = require('./moddn_request');
var ModifyDNResponse = require('./moddn_response');
var SearchRequest = require('./search_request');
var SearchEntry = require('./search_entry');
var SearchResponse = require('./search_response');
var UnbindRequest = require('./unbind_request');
var UnbindResponse = require('./unbind_response');
var Message = require('./message');
var Protocol = require('../protocol');
// Just make sure this adds to the prototype
require('buffertools');
///--- Globals
var Ber = asn1.Ber;
var BerReader = asn1.BerReader;
///--- API
function Parser(options) {
if (!options || typeof(options) !== 'object')
throw new TypeError('options (object) required');
if (!options.log4js || typeof(options.log4js) !== 'object')
throw new TypeError('options.log4js (object) required');
EventEmitter.call(this);
this._reset();
var self = this;
this.log4js = options.log4js;
this.log = this.log4js.getLogger('Parser');
}
util.inherits(Parser, EventEmitter);
module.exports = Parser;
Parser.prototype.write = function(data) {
if (!data || !Buffer.isBuffer(data))
throw new TypeError('data (buffer) required');
var self = this;
if (this._buffer)
data = this._buffer.concat(data);
if (this.log.isTraceEnabled())
this.log.trace('Processing buffer (concat\'d): ' + util.inspect(data));
// If there's more than one message in this buffer
var extra;
try {
if (this._message === null) {
var ber = new BerReader(data);
if (!this._newMessage(ber))
return false;
data = data.slice(ber.offset);
}
if (data.length > this._messageLength) {
extra = data.slice(ber.length);
data = data.slice(0, ber.length);
}
if (!this._message.parse(data, ber.length))
this.emit('protocolError', new Error('TODO'));
var message = this._message;
this._reset();
this.emit('message', message);
} catch (e) {
if (e.name === 'InvalidAsn1Error') {
self.emit('protocolError', e, self._message);
} else {
self.emit('error', e);
}
return false;
}
// Another message is already there
if (extra) {
if (this.log.isTraceEnabled())
this.log.trace('parsing extra bytes: ' + util.inspect(extra));
return this.write(extra);
}
return true;
};
Parser.prototype._newMessage = function(ber) {
assert.ok(ber);
if (this._messageLength === null) {
if (ber.readSequence() === null) { // not enough data for the length?
this._buffer = ber.buffer;
if (this.log.isTraceEnabled())
this.log.trace('Not enough data for the message header');
return false;
}
this._messageLength = ber.length;
}
if (ber.remain < this._messageLength) {
if (this.log.isTraceEnabled())
this.log.trace('Not enough data for the message');
this._buffer = ber.buffer;
return false;
}
var messageID = ber.readInt();
var type = ber.readSequence();
if (this.log.isTraceEnabled())
this.log.trace('message id=%d, type=0x%s', messageID, type.toString(16));
var Message;
switch (type) {
case Protocol.LDAP_REQ_ADD:
Message = AddRequest;
break;
case Protocol.LDAP_REP_ADD:
Message = AddResponse;
break;
case Protocol.LDAP_REQ_BIND:
Message = BindRequest;
break;
case Protocol.LDAP_REP_BIND:
Message = BindResponse;
break;
case Protocol.LDAP_REQ_COMPARE:
Message = CompareRequest;
break;
case Protocol.LDAP_REP_COMPARE:
Message = CompareResponse;
break;
case Protocol.LDAP_REQ_DELETE:
Message = DeleteRequest;
break;
case Protocol.LDAP_REP_DELETE:
Message = DeleteResponse;
break;
case Protocol.LDAP_REQ_EXTENSION:
Message = ExtendedRequest;
break;
case Protocol.LDAP_REP_EXTENSION:
Message = ExtendedResponse;
break;
case Protocol.LDAP_REQ_MODIFY:
Message = ModifyRequest;
break;
case Protocol.LDAP_REP_MODIFY:
Message = ModifyResponse;
break;
case Protocol.LDAP_REQ_MODRDN:
Message = ModifyDNRequest;
break;
case Protocol.LDAP_REP_MODRDN:
Message = ModifyDNResponse;
break;
case Protocol.LDAP_REQ_SEARCH:
Message = SearchRequest;
break;
case Protocol.LDAP_REP_SEARCH_ENTRY:
Message = SearchEntry;
break;
case Protocol.LDAP_REP_SEARCH:
Message = SearchResponse;
break;
case Protocol.LDAP_REQ_UNBIND:
Message = UnbindRequest;
break;
default:
var e = new Error('protocolOp 0x' + type.toString(16) + ' not supported');
this.emit('protocolError', e, messageID);
this._reset();
return false;
}
assert.ok(Message);
var self = this;
this._message = new Message({
messageID: messageID,
log4js: self.log4js
});
return true;
};
Parser.prototype._reset = function() {
this._message = null;
this._messageLength = null;
this._buffer = null;
};

117
lib/messages/result.js Normal file
View File

@ -0,0 +1,117 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
var BerWriter = asn1.BerWriter;
///--- API
function LDAPResult(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options (object) required');
if (options.status && typeof(options.status) !== 'number')
throw new TypeError('options.status must be a number');
if (options.matchedDN && typeof(options.matchedDN) !== 'string')
throw new TypeError('options.matchedDN must be a string');
if (options.errorMessage && typeof(options.errorMessage) !== 'string')
throw new TypeError('options.errorMessage must be a string');
if (options.referrals) {
if (!(options.referrals instanceof Array))
throw new TypeError('options.referrrals must be an array[string]');
options.referrals.forEach(function(r) {
if (typeof(r) !== 'string')
throw new TypeError('options.referrals must be an array[string]');
});
}
} else {
options = {};
}
LDAPMessage.call(this, options);
this.status = options.status || 0; // LDAP SUCCESS
this.matchedDN = options.matchedDN || '';
this.errorMessage = options.errorMessage || '';
this.referrals = options.referrals || [];
this.__defineGetter__('type', function() { return 'LDAPResult'; });
}
util.inherits(LDAPResult, LDAPMessage);
module.exports = LDAPResult;
LDAPResult.prototype.end = function(status) {
assert.ok(this.connection);
if (typeof(status) === 'number')
this.status = status;
var ber = this.toBer();
if (this.log.isDebugEnabled())
this.log.debug('%s: sending: %j', this.connection.ldap.id, this.json);
this.connection.write(ber);
};
LDAPResult.prototype._parse = function(ber) {
assert.ok(ber);
this.status = ber.readEnumeration();
this.matchedDN = ber.readString();
this.errorMessage = ber.readString();
var t = ber.peek();
if (t === Protocol.LDAP_REP_REFERRAL) {
var 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.LDAP_REP_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;
};

View File

@ -0,0 +1,110 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var Attribute = require('../attribute');
var dn = require('../dn');
var Protocol = require('../protocol');
///--- Globals
var BerWriter = asn1.BerWriter;
///--- API
function SearchEntry(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
if (options.objectName && !(options.objectName instanceof dn.DN))
throw new TypeError('options.objectName must be a DN');
if (options.attributes && !Array.isArray(options.attributes))
throw new TypeError('options.attributes must be an array[Attribute]');
if (options.attributes && options.attributes.length) {
options.attributes.forEach(function(a) {
if (!(a instanceof Attribute))
throw new TypeError('options.attributes must be an array[Attribute]');
});
}
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REP_SEARCH_ENTRY;
LDAPMessage.call(this, options);
this.objectName = options.objectName || null;
this.attributes = options.attributes ? options.attributes.slice(0) : [];
var self = this;
this.__defineGetter__('type', function() { return 'SearchEntry'; });
this.__defineGetter__('_dn', function() {
return self.objectName.toString();
});
}
util.inherits(SearchEntry, LDAPMessage);
module.exports = SearchEntry;
SearchEntry.prototype.addAttribute = function(attr) {
if (!attr || typeof(attr) !== 'object')
throw new TypeError('attr (attribute) required');
this.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);
});
return j;
};
SearchEntry.prototype._parse = function(ber) {
assert.ok(ber);
this.objectName = ber.readString();
assert.ok(ber.readSequence());
var end = ber.offset + ber.length;
while (ber.offset < end) {
var a = new Attribute();
a.parse(ber);
this.attributes.push(a);
}
return true;
};
SearchEntry.prototype._toBer = function(ber) {
assert.ok(ber);
ber.writeString(this.objectName.toString());
ber.startSequence();
this.attributes.forEach(function(a) {
// This may or may not be an attribute
ber = Attribute.toBer(a, ber);
});
ber.endSequence();
return ber;
};

View File

@ -0,0 +1,154 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var filters = require('../filters');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API
function SearchRequest(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REQ_SEARCH;
LDAPMessage.call(this, options);
var self = this;
this.__defineGetter__('type', function() { return 'SearchRequest'; });
this.__defineGetter__('_dn', function() {
return self.baseObject;
});
this.__defineGetter__('scope', function() {
switch (self._scope) {
case Protocol.SCOPE_BASE_OBJECT: return 'base';
case Protocol.SCOPE_ONE_LEVEL: return 'one';
case Protocol.SCOPE_SUBTREE: return 'sub';
default:
throw new Error(self._scope + ' is an invalid search scope');
}
});
this.__defineSetter__('scope', function(s) {
if (typeof(s) === 'string') {
switch (s) {
case 'base':
self._scope = Protocol.SCOPE_BASE_OBJECT;
break;
case 'one':
self._scope = Protocol.SCOPE_ONE_LEVEL;
break;
case 'sub':
self._scope = Protocol.SCOPE_SUBTREE;
break;
default:
throw new Error(s + ' is an invalid search scope');
}
} else {
self._scope = s;
}
});
this.baseObject = options.baseObject || new dn.DN([{}]);
this.scope = options.scope || 'base';
this.derefAliases = options.derefAliases || Protocol.NEVER_DEREF_ALIASES;
this.sizeLimit = options.sizeLimit || 0;
this.timeLimit = options.timeLimit || 00;
this.typesOnly = options.typesOnly || false;
this.filter = options.filter || null;
this.attributes = options.attributes ? options.attributes.slice(0) : [];
}
util.inherits(SearchRequest, LDAPMessage);
module.exports = SearchRequest;
SearchRequest.prototype.newResult = function() {
var self = this;
return new LDAPResult({
messageID: self.messageID,
protocolOp: Protocol.LDAP_REP_SEARCH
});
};
SearchRequest.prototype._parse = function(ber) {
assert.ok(ber);
this.baseObject = dn.parse(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.parse(ber);
// look for attributes
if (ber.readSequence() === (Ber.Sequence | Ber.Constructor)) {
var 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);
ber.writeString(this.baseObject.toString());
ber.writeEnumeration(this._scope);
ber.writeEnumeration(this.derefAliases);
ber.writeInt(this.sizeLimit);
ber.writeInt(this.timeLimit);
ber.writeBoolean(this.typesOnly);
var f = this.filter || new filters.PresenceFilter({attribute: 'objectclass'});
ber = f.toBer(ber);
if (this.attributes && this.attributes.length) {
ber.startSequence(Ber.Sequence | Ber.Constructor);
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;
};

View File

@ -0,0 +1,60 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var LDAPResult = require('./result');
var SearchEntry = require('./search_entry');
var Protocol = require('../protocol');
///--- API
function SearchResponse(options) {
if (!options)
options = {};
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
options.protocolOp = Protocol.LDAP_REP_SEARCH;
LDAPResult.call(this, options);
}
util.inherits(SearchResponse, LDAPResult);
module.exports = SearchResponse;
/**
* Allows you to send a SearchEntry back to the client.
*
* @param {Object} entry an instance of SearchEntry.
*/
SearchResponse.prototype.send = function(entry) {
if (!entry || !(entry instanceof SearchEntry))
throw new TypeError('entry (SearchEntry) required');
if (entry.messageID !== this.messageID)
throw new Error('SearchEntry messageID mismatch');
assert.ok(this.connection);
if (this.log.isDebugEnabled())
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.json);
this.connection.write(entry.toBer());
};
SearchResponse.prototype.createSearchEntry = function(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
} else {
options = {};
}
options.messageID = this.messageID;
options.log4js = this.log4js;
return new SearchEntry(options);
};

View File

@ -0,0 +1,83 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API
function UnbindRequest(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
} else {
options = {};
}
options.protocolOp = Protocol.LDAP_REQ_UNBIND;
LDAPMessage.call(this, options);
this.__defineGetter__('type', function() { return 'UnbindRequest'; });
this.__defineGetter__('_dn', function() { return ''; });
}
util.inherits(UnbindRequest, LDAPMessage);
module.exports = UnbindRequest;
UnbindRequest.prototype.newResult = function() {
var self = this;
// This one is special, so just hack up the result object
function UnbindResponse(options) {
LDAPMessage.call(this, options);
this.__defineGetter__('type', function() { return 'UnbindResponse'; });
}
util.inherits(UnbindResponse, LDAPMessage);
UnbindResponse.prototype.end = function(status) {
if (this.log.isTraceEnabled())
log.trace('%s: unbinding!', this.connection.ldap.id);
this.connection.end();
};
UnbindResponse.prototype._json = function(j) { return j; };
return new UnbindResponse({
messageID: 0,
protocolOp: 0,
status: 0 // Success
});
};
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;
};

View File

@ -0,0 +1,46 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var LDAPMessage = require('./result');
var Protocol = require('../protocol');
///--- 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) {
if (!options)
options = {};
if (typeof(options) !== 'object')
throw new TypeError('options must be an object');
options.protocolOp = 0;
LDAPMessage.call(this, options);
this.__defineGetter__('type', function() { return 'UnbindResponse'; });
}
util.inherits(UnbindResponse, LDAPMessage);
module.exports = UnbindResponse;
/**
* Special override that just ends the connection, if present.
*
* @param {Number} status completely ignored.
*/
UnbindResponse.prototype.end = function(status) {
assert.ok(this.connection);
if (this.log.isTraceEnabled())
this.log.trace('%s: unbinding!', this.connection.ldap.id);
this.connection.end();
};
UnbindResponse.prototype._json = function(j) {
return j;
};

54
lib/protocol.js Normal file
View File

@ -0,0 +1,54 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
module.exports = {
// Misc
LDAP_VERSION_3: 0x03,
LBER_SET: 0x31,
LDAP_CONTROLS: 0xa0,
// Search
SCOPE_BASE_OBJECT: 0,
SCOPE_ONE_LEVEL: 1,
SCOPE_SUBTREE: 2,
NEVER_DEREF_ALIASES: 0,
DEREF_IN_SEARCHING: 1,
DEREF_BASE_OBJECT: 2,
DEREF_ALWAYS: 3,
FILTER_AND: 0xa0,
FILTER_OR: 0xa1,
FILTER_NOT: 0xa2,
FILTER_EQUALITY: 0xa3,
FILTER_SUBSTRINGS: 0xa4,
FILTER_GE: 0xa5,
FILTER_LE: 0xa6,
FILTER_PRESENT: 0x87,
FILTER_APPROX: 0xa8,
FILTER_EXT: 0xa9,
// Protocol Operations
LDAP_REQ_BIND: 0x60,
LDAP_REQ_UNBIND: 0x42,
LDAP_REQ_SEARCH: 0x63,
LDAP_REQ_MODIFY: 0x66,
LDAP_REQ_ADD: 0x68,
LDAP_REQ_DELETE: 0x4a,
LDAP_REQ_MODRDN: 0x6c,
LDAP_REQ_COMPARE: 0x6e,
LDAP_REQ_ABANDON: 0x50,
LDAP_REQ_EXTENSION: 0x77,
LDAP_REP_BIND: 0x61,
LDAP_REP_SEARCH_ENTRY: 0x64,
LDAP_REP_SEARCH_REF: 0x73,
LDAP_REP_SEARCH: 0x65,
LDAP_REP_MODIFY: 0x67,
LDAP_REP_ADD: 0x69,
LDAP_REP_DELETE: 0x6b,
LDAP_REP_MODRDN: 0x6d,
LDAP_REP_COMPARE: 0x6f,
LDAP_REP_EXTENSION: 0x78
};

509
lib/server.js Normal file
View File

@ -0,0 +1,509 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var EventEmitter = require('events').EventEmitter;
var net = require('net');
var tls = require('tls');
var util = require('util');
var asn1 = require('asn1');
var sprintf = require('sprintf').sprintf;
var dn = require('./dn');
var errors = require('./errors');
var Protocol = require('./protocol');
var logStub = require('./log_stub');
var Parser = require('./messages').Parser;
var AddResponse = require('./messages/add_response');
var BindResponse = require('./messages/bind_response');
var CompareResponse = require('./messages/compare_response');
var DeleteResponse = require('./messages/del_response');
var ExtendedResponse = require('./messages/ext_response');
var ModifyResponse = require('./messages/modify_response');
var ModifyDNResponse = require('./messages/moddn_response');
var SearchResponse = require('./messages/search_response');
var UnbindResponse = require('./messages/unbind_response');
///--- Globals
var Ber = asn1.Ber;
var BerReader = asn1.BerReader;
///--- Helpers
function setupConnection(server, c, config) {
assert.ok(server);
assert.ok(c);
assert.ok(config);
c.ldap = {
id: c.remoteAddress + ':' + c.remotePort,
config: config
};
c.addListener('timeout', function() {
server.log.trace('%s timed out', c.ldap.id);
c.destroy();
});
c.addListener('end', function() {
server.log.trace('%s shutdown', c.ldap.id);
});
c.addListener('error', function(err) {
server.log.warn('%s unexpected connection error', c.ldap.id, err);
c.destroy();
});
c.addListener('close', function(had_err) {
server.log.trace('%s close; had_err=%j', c.ldap.id, had_err);
c.destroy();
});
return c;
}
function getResponse(req) {
assert.ok(req);
var Response;
switch (req.protocolOp) {
case Protocol.LDAP_REQ_BIND:
Response = BindResponse;
break;
case Protocol.LDAP_REQ_ABANDON:
return; // Noop
case Protocol.LDAP_REQ_ADD:
Response = AddResponse;
break;
case Protocol.LDAP_REQ_COMPARE:
Response = CompareResponse;
break;
case Protocol.LDAP_REQ_DELETE:
Response = DeleteResponse;
break;
case Protocol.LDAP_REQ_EXTENSION:
Response = ExtendedResponse;
break;
case Protocol.LDAP_REQ_MODIFY:
Response = ModifyResponse;
break;
case Protocol.LDAP_REQ_MODRDN:
Response = ModifyDNResponse;
break;
case Protocol.LDAP_REQ_SEARCH:
Response = SearchResponse;
break;
case Protocol.LDAP_REQ_UNBIND:
Response = UnbindResponse;
break;
default:
return null;
}
assert.ok(Response);
var res = new Response({
messageID: req.messageID,
log4js: req.log4js
});
res.connection = req.connection;
res.logId = req.logId;
return res;
}
function defaultHandler(req, res, next) {
assert.ok(req);
assert.ok(res);
assert.ok(next);
res.matchedDN = req.dn.toString();
res.errorMessage = 'Server method not implemented';
res.end(errors.LDAP_OTHER);
return next();
}
function noSuffixHandler(req, res, next) {
assert.ok(req);
assert.ok(res);
assert.ok(next);
res.errorMessage = 'No tree found for: ' + req.dn.toString();
res.end(errors.LDAP_NO_SUCH_OBJECT);
return next();
}
function noExOpHandler(req, res, next) {
assert.ok(req);
assert.ok(res);
assert.ok(next);
res.errorMessage = req.requestName + ' not supported';
res.end(errors.LDAP_PROTOCOL_ERROR);
return next();
}
function getHandlerChain(server, req) {
assert.ok(server);
assert.ok(req);
var backend;
var handlers;
var matched = false;
for (var r in server.routes) {
if (server.routes.hasOwnProperty(r)) {
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
if (r === req.requestName)
matched = true;
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
matched = true;
} else {
if (req.dn) {
if (r === req.dn.toString()) {
matched = true;
} else if (server.routes[r]._dn &&
server.routes[r]._dn.parentOf(req.dn)) {
matched = true;
}
}
}
if (!matched)
continue;
switch (req.protocolOp) {
case Protocol.LDAP_REQ_BIND:
handlers = server.routes[r]._bind;
break;
case Protocol.LDAP_REQ_ABANDON:
return; // Noop
case Protocol.LDAP_REQ_ADD:
handlers = server.routes[r]._add;
break;
case Protocol.LDAP_REQ_COMPARE:
handlers = server.routes[r]._compare;
break;
case Protocol.LDAP_REQ_DELETE:
handlers = server.routes[r]._del;
break;
case Protocol.LDAP_REQ_EXTENSION:
handlers = server.routes[r]._exop;
break;
case Protocol.LDAP_REQ_MODIFY:
handlers = server.routes[r]._modify;
break;
case Protocol.LDAP_REQ_MODRDN:
handlers = server.routes[r]._modifyDN;
break;
case Protocol.LDAP_REQ_SEARCH:
handlers = server.routes[r]._search;
break;
case Protocol.LDAP_REQ_UNBIND:
if (server.routes['unbind'])
handlers = server.routes['unbind']._unbind;
break;
default:
server.log.warn('Unimplemented server method: %s', req.type);
return c.destroy();
}
}
if (handlers) {
backend = server.routes[r]._backend;
break;
}
}
if (!handlers) {
backend = server;
if (matched) {
server.log.warn('No handler registered for %s:%s, running default',
req.type, req.dn.toString());
handlers = [defaultHandler];
} else {
server.log.trace('%s does not map to a known suffix/oid',
req.dn.toString());
handlers = [req.protocolOp !== Protocol.LDAP_REQ_EXTENSION ?
noSuffixHandler : noExOpHandler];
}
}
assert.ok(backend);
assert.ok(handlers);
assert.ok(handlers instanceof Array);
assert.ok(handlers.length);
return {
backend: backend,
handlers: handlers
};
}
function addHandlers(server) {
assert.ok(server);
assert.ok(server.log);
var log = server.log;
var ops = [ // We don't support abandon.
'add',
'bind',
'compare',
'del',
'exop',
'modify',
'modifyDN',
'search',
'unbind'
];
function processHandlerChain(chain) {
if (!chain)
return [defaultHandler];
if (chain instanceof Array) {
if (!chain.length)
return [defaultHandler];
chain.forEach(function(f) {
if (typeof(f) !== 'function')
throw new TypeError('[function(req, res, next)] required');
});
return chain;
} else if (typeof(chain) === 'function') {
return [chain];
}
throw new TypeError('[function(req, res, next)] required');
}
server.routes = {};
ops.forEach(function(o) {
var op = '_' + o;
server[o] = function(name, handler) {
if (o === 'unbind') {
if (typeof(name === 'function')) {
handler = name;
name = 'unbind';
}
}
if (!name || typeof(name) !== 'string')
throw new TypeError('name (string) required');
if (!handler || typeof(handler) !== 'function')
throw new TypeError('[function(req, res, next)] required');
// Do this first so it will throw
var _dn = null;
if (o !== 'exop' && o !== 'unbind') {
_dn = dn.parse(name);
name = _dn.toString();
}
if (!server.routes[name])
server.routes[name] = {};
if (!server.routes[name]._backend)
server.routes[name]._backend = server;
server.routes[name][op] = processHandlerChain(handler);
server.routes[name]._dn = _dn;
if (log.isTraceEnabled()) {
var _names = [];
server.routes[name][op].forEach(function(f) {
_names.push(f.name || 'Anonymous Function');
});
log.trace('%s(%s) -> %s', o, name, _names);
}
};
});
server.mount = function(name, backend) {
if (!name || typeof(name) !== 'string')
throw new TypeError('name (string) required');
if (!backend || typeof(backend) !== 'object')
throw new TypeError('backend (object) required');
if (!backend.name)
throw new TypeError('backend is not a valid LDAP Backend');
if (!backend.register || typeof(backend.register) !== 'function')
throw new TypeError('backend is not a valid LDAP Backend');
var _dn = null;
// Do this first so it will throw
_dn = dn.parse(name);
name = _dn.toString();
ops.forEach(function(o) {
if (o === 'exop' || o === 'unbind')
return;
var op = '_' + o;
if (!server.routes[name])
server.routes[name] = {};
if (!server.routes[name]._backend)
server.routes[name]._backend = backend;
server.routes[name][op] = processHandlerChain(backend.register(o));
if (log.isTraceEnabled()) {
var _names = [];
server.routes[name][op].forEach(function(f) { _names.push(f.name); });
log.trace('%s(%s) -> %s', o, name, _names);
}
});
server.routes[name]._dn = _dn;
log.info('%s mounted at %s', server.routes[name]._backend.toString(), dn);
return server;
};
return server;
}
///--- API
module.exports = {
createServer: function(options) {
if (options) {
if (typeof(options) !== 'object')
throw new TypeError('options (object) required');
if (options.log4js && typeof(options.log4js) !== 'object')
throw new TypeError('options.log4s must be an object');
if (options.certificate || options.key) {
if (!(options.certificate && options.key) ||
typeof(options.certificate) !== 'string' ||
typeof(options.key) !== 'string') {
throw new TypeError('options.certificate and options.key (string) ' +
'are both required for TLS');
}
}
} else {
options = {};
}
var server;
function newConnection(c) {
assert.ok(c);
if (c.type === 'unix' && server.type === 'unix') {
c.remoteAddress = server.path;
c.remotePort = c.fd;
}
assert.ok(c.remoteAddress);
assert.ok(c.remotePort);
setupConnection(server, c, options);
if (server.log.isTraceEnabled())
server.log.trace('new connection from %s', c.ldap.id);
c.parser = new Parser({
log4js: server.log4js
});
c.parser.on('message', function(req) {
assert.ok(req);
req.connection = c;
req.logId = c.remoteAddress + '::' + req.messageID;
if (server.log.isDebugEnabled())
server.log.debug('%s: message received: req=%j', c.ldap.id, req.json);
var res = getResponse(req);
if (!res) {
server.log.warn('Unimplemented server method: %s', req.type);
c.destroy();
return;
}
var chain = getHandlerChain(server, req);
var i = 0;
return function(err) {
if (err) {
res.status = err.code || errors.LDAP_OPERATIONS_ERROR;
res.matchedDN = err.dn ? err.dn.toString() : req.dn.toString();
res.errorMessage = err.message || '';
return res.end();
}
var next = arguments.callee;
if (chain.handlers[i])
return chain.handlers[i++].call(chain.backend, req, res, next);
}();
});
c.parser.on('protocolError', function(err, messageID) {
server.log.warn('%s sent invalid protocol message', c.ldap.id, err);
// TODO (mcavage) deal with this
// send an unsolicited notification
c.destroy();
});
c.parser.on('error', function(err) {
server.log.error('Exception happened parsing for %s: %s',
c.ldap.id, err.stack);
c.destroy();
});
c.on('data', function(data) {
assert.ok(data);
if (server.log.isTraceEnabled())
server.log.trace('data on %s: %s', c.ldap.id, util.inspect(data));
c.parser.write(data);
});
}; // end newConnection
var secure = options.certificate && options.key;
if (secure) {
server = tls.createServer(options, newConnection);
} else {
server = net.createServer(newConnection);
}
server.log4js = options.log4js || logStub;
server.ldap = {
config: options
};
server.__defineGetter__('log', function() {
if (!server._log)
server._log = server.log4js.getLogger('LDAPServer');
return server._log;
});
addHandlers(server);
return server;
}
};

64
lib/url.js Normal file
View File

@ -0,0 +1,64 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var querystring = require('querystring');
var url = require('url');
var util = require('util');
var dn = require('./dn');
module.exports = {
parse: function(urlStr, parseDN) {
var u = url.parse(urlStr);
if (!u.protocol || !(u.protocol === 'ldap:' || u.protocol === 'ldaps:'))
throw new TypeError(urlStr + ' is an invalid LDAP url (protocol)');
if (!u.port) {
u.port = 389;
} else {
u.port = parseInt(u.port, 10);
}
if (!u.hostname)
u.hostname = 'localhost';
u.secure = (u.protocol === 'ldaps:');
if (u.pathname) {
u.pathname = querystring.unescape(u.pathname.substr(1));
u.DN = parseDN ? dn.parse(u.pathname) : u.pathname;
}
if (u.search) {
u.attributes = [];
var tmp = u.search.substr(1).split('?');
if (tmp && tmp.length) {
if (tmp[0]) {
tmp[0].split(',').forEach(function(a) {
u.attributes.push(querystring.unescape(a.trim()));
});
}
}
if (tmp[1]) {
if (tmp[1] !== 'base' && tmp[1] !== 'one' && tmp[1] !== 'sub')
throw new TypeError(urlStr + ' is an invalid LDAP url (scope)');
u.scope = tmp[1];
}
if (tmp[2]) {
u.filter = querystring.unescape(tmp[2]);
}
if (tmp[3]) {
u.extensions = querystring.unescape(tmp[3]);
}
if (!u.scope)
u.scope = 'base';
if (!u.filter)
u.filter = '(objectclass=*)';
}
return u;
}
};

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"author": "Mark Cavage <mcavage@gmail.com>",
"name": "ldapjs",
"description": "LDAP client and server APIs",
"version": "0.0.1",
"repository": {
"type": "git",
"url": "git://github.com/mcavage/node-ldapjs.git"
},
"main": "lib/index.js",
"engines": {
"node": ">=0.4.10"
},
"dependencies": {
"asn1": "~0.1.5",
"buffertools": "~1.0.3",
"sprintf": "~0.1.1"
},
"devDependencies": {
"tap": "~0.0.9",
"node-uuid": "~1.2.0"
},
"scripts": {
"pretest": "which gjslint; if [[ \"$?\" = 0 ]] ; then gjslint --nojsdoc -r lib -r tst; else echo \"Missing gjslint. Skipping lint\"; fi",
"test": "./node_modules/.bin/tap ./tst"
}
}

82
tst/attribute.test.js Normal file
View File

@ -0,0 +1,82 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var Attribute;
///--- Tests
test('load library', function(t) {
Attribute = require('../lib/index').Attribute;
t.ok(Attribute);
t.end();
});
test('new no args', function(t) {
t.ok(new Attribute());
t.end();
});
test('new with args', function(t) {
var 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.end();
});
test('toBer', function(t) {
var attr = new Attribute({
type: 'cn',
vals: ['foo', 'bar']
});
t.ok(attr);
var ber = new BerWriter();
attr.toBer(ber);
var 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) {
var ber = new BerWriter();
ber.startSequence();
ber.writeString('cn');
ber.startSequence(0x31);
ber.writeStringArray(['foo', 'bar']);
ber.endSequence();
ber.endSequence();
var 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();
});

100
tst/change.test.js Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var Attribute;
var Change;
///--- Tests
test('load library', function(t) {
Attribute = require('../lib/index').Attribute;
Change = require('../lib/index').Change;
t.ok(Attribute);
t.ok(Change);
t.end();
});
test('new no args', function(t) {
t.ok(new Change());
t.end();
});
test('new with args', function(t) {
var change = new Change({
operation: 0x00,
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('toBer', function(t) {
var change = new Change({
operation: 'Add',
modification: new Attribute({
type: 'cn',
vals: ['foo', 'bar']
})
});
t.ok(change);
var ber = new BerWriter();
change.toBer(ber);
var 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) {
var 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();
var 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();
});

320
tst/client.test.js Normal file
View File

@ -0,0 +1,320 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var uuid = require('node-uuid');
///--- Globals
var BIND_DN = 'cn=root';
var BIND_PW = 'secret';
var SOCKET = '/tmp/.' + uuid();
var SUFFIX = 'dc=test';
var ldap;
var Attribute;
var Change;
var client;
var server;
///--- Tests
test('setup', function(t) {
ldap = require('../lib/index');
t.ok(ldap);
t.ok(ldap.createClient);
t.ok(ldap.createServer);
t.ok(ldap.Attribute);
t.ok(ldap.Change);
Attribute = ldap.Attribute;
Change = ldap.Change;
server = ldap.createServer();
t.ok(server);
server.bind(BIND_DN, function(req, res, next) {
if (req.credentials !== BIND_PW)
return next(new ldap.InvalidCredentialsError('Invalid password'));
res.end();
return next();
});
server.add(SUFFIX, function(req, res, next) {
res.end();
return next();
});
server.compare(SUFFIX, function(req, res, next) {
if (req.value !== 'test')
return next(new ldap.CompareFalseError('value was test'));
res.end(ldap.LDAP_COMPARE_TRUE);
return next();
});
server.del(SUFFIX, function(req, res, next) {
res.end();
return next();
});
// 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.end();
return next();
});
server.modify(SUFFIX, function(req, res, next) {
res.end();
return next();
});
server.modifyDN(SUFFIX, function(req, res, next) {
res.end();
return next();
});
server.search(SUFFIX, function(req, res, next) {
var e = res.createSearchEntry({
objectName: req.dn,
attributes: [
new Attribute({
type: 'cn',
vals: ['test']
})
]
});
res.send(e);
res.send(e);
res.end();
return next();
});
server.unbind(function(req, res, next) {
res.end();
return next();
});
server.listen(SOCKET, function() {
client = ldap.createClient({
socketPath: SOCKET
});
t.ok(client);
// client.log4js.setLevel('Debug');
t.end();
});
});
test('simple bind success', function(t) {
client.bind(BIND_DN, BIND_PW, function(err, res) {
t.ifError(err);
t.ok(res);
t.equal(res.status, 0);
t.end();
});
});
test('simple bind failure', function(t) {
client.bind(BIND_DN, uuid(), function(err, res) {
t.ok(err);
t.notOk(res);
t.ok(err instanceof ldap.InvalidCredentialsError);
t.ok(err instanceof Error);
t.ok(err.dn);
t.ok(err.message);
t.ok(err.stack);
t.end();
});
});
test('add success', function(t) {
var attrs = [
new Attribute({
type: 'cn',
vals: ['test']
})
];
client.add('cn=add, ' + SUFFIX, attrs, function(err, res) {
t.ifError(err);
t.ok(res);
t.equal(res.status, 0);
t.end();
});
});
test('compare success', function(t) {
client.compare('cn=compare, ' + SUFFIX, 'cn', 'test', function(err,
matched,
res) {
t.ifError(err);
t.ok(matched);
t.ok(res);
t.end();
});
});
test('compare false', function(t) {
client.compare('cn=compare, ' + SUFFIX, 'cn', 'foo', function(err,
matched,
res) {
t.ifError(err);
t.notOk(matched);
t.ok(res);
t.end();
});
});
test('compare bad suffix', function(t) {
client.compare('cn=' + uuid(), 'cn', 'foo', function(err,
matched,
res) {
t.ok(err);
t.ok(err instanceof ldap.NoSuchObjectError);
t.notOk(matched);
t.notOk(res);
t.end();
});
});
test('delete success', function(t) {
client.del('cn=delete, ' + SUFFIX, function(err, res) {
t.ifError(err);
t.ok(res);
t.end();
});
});
test('exop success', function(t) {
client.exop('1.3.6.1.4.1.4203.1.11.3', function(err, value, res) {
t.ifError(err);
t.ok(value);
t.ok(res);
t.equal(value, 'u:xxyyz@EXAMPLE.NET');
t.end();
});
});
test('exop invalid', function(t) {
client.exop('1.2.3.4', function(err, res) {
t.ok(err);
t.ok(err instanceof ldap.ProtocolError);
t.notOk(res);
t.end();
});
});
test('modify success', function(t) {
var change = new Change({
type: 'Replace',
modification: new Attribute({
type: 'cn',
vals: ['test']
})
});
client.modify('cn=modify, ' + SUFFIX, change, function(err, res) {
t.ifError(err);
t.ok(res);
t.equal(res.status, 0);
t.end();
});
});
test('modify array success', function(t) {
var changes = [
new Change({
operation: 'Replace',
modification: new Attribute({
type: 'cn',
vals: ['test']
})
}),
new Change({
operation: 'Delete',
modification: new Attribute({
type: 'sn'
})
})
];
client.modify('cn=modify, ' + SUFFIX, changes, function(err, res) {
t.ifError(err);
t.ok(res);
t.equal(res.status, 0);
t.end();
});
});
test('modify DN new RDN only', function(t) {
client.modifyDN('cn=old, ' + SUFFIX, 'cn=new', function(err, res) {
t.ifError(err);
t.ok(res);
t.equal(res.status, 0);
t.end();
});
});
test('modify DN new superior', function(t) {
client.modifyDN('cn=old, ' + SUFFIX, 'cn=new, dc=foo', function(err, res) {
t.ifError(err);
t.ok(res);
t.equal(res.status, 0);
t.end();
});
});
test('search basic', function(t) {
client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function(err, res) {
t.ifError(err);
t.ok(res);
var gotEntry = 0;
res.on('searchEntry', function(entry) {
t.ok(entry);
t.ok(entry instanceof ldap.SearchEntry);
t.equal(entry.dn.toString(), 'cn=test, ' + SUFFIX);
t.ok(entry.attributes);
t.ok(entry.attributes.length);
gotEntry++;
});
res.on('error', function(err) {
t.fail(err);
});
res.on('end', function(res) {
t.ok(res);
t.ok(res instanceof ldap.SearchResponse);
t.equal(res.status, 0);
t.equal(gotEntry, 2);
t.end();
});
});
});
test('shutdown', function(t) {
client.unbind(function() {
server.on('close', function() {
t.end();
});
server.close();
});
});

59
tst/control.test.js Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var Control;
///--- Tests
test('load library', function(t) {
Control = require('../lib/index').Control;
t.ok(Control);
t.end();
});
test('new no args', function(t) {
t.ok(new Control());
t.end();
});
test('new with args', function(t) {
var c = new Control({
type: '2.16.840.1.113730.3.4.2',
criticality: true
});
t.ok(c);
t.equal(c.type, '2.16.840.1.113730.3.4.2');
t.ok(c.criticality);
t.end();
});
test('parse', function(t) {
var ber = new BerWriter();
ber.startSequence();
ber.writeString('2.16.840.1.113730.3.4.2');
ber.writeBoolean(true);
ber.writeString('foo');
ber.endSequence();
var c = new Control();
t.ok(c);
t.ok(c.parse(new BerReader(ber.buffer)));
t.ok(c);
t.equal(c.type, '2.16.840.1.113730.3.4.2');
t.ok(c.criticality);
t.equal(c.value, 'foo');
t.end();
});

79
tst/dn.test.js Normal file
View File

@ -0,0 +1,79 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
///--- Globals
var dn;
///--- Tests
test('load library', function(t) {
dn = require('../lib/index').dn;
t.ok(dn);
t.end();
});
test('parse basic', function(t) {
var DN_STR = 'cn=mark, ou=people, o=joyent';
var 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) {
var DN_STR = 'cn=m\\,ark, ou=people, o=joyent';
var 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) {
var DN_STR = 'cn=mark+sn=cavage, ou=people, o=joyent';
var 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) {
var DN_STR = 'cn="mark+sn=cavage", ou=people, o=joyent';
var 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();
});

81
tst/filters/and.test.js Normal file
View File

@ -0,0 +1,81 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var EqualityFilter;
var AndFilter;
///--- Tests
test('load library', function(t) {
var filters = require('../../lib/index').filters;
t.ok(filters);
EqualityFilter = filters.EqualityFilter;
AndFilter = filters.AndFilter;
t.ok(EqualityFilter);
t.ok(AndFilter);
t.end();
});
test('Construct no args', function(t) {
t.ok(new AndFilter());
t.end();
});
test('Construct args', function(t) {
var f = new AndFilter();
f.addFilter(new EqualityFilter({
attribute: 'foo',
value: 'bar'
}));
f.addFilter(new EqualityFilter({
attribute: 'zig',
value: 'zag'
}));
t.ok(f);
t.equal(f.toString(), '(&(foo=bar)(zig=zag))');
t.end();
});
test('match true', function(t) {
var f = new AndFilter();
f.addFilter(new EqualityFilter({
attribute: 'foo',
value: 'bar'
}));
f.addFilter(new EqualityFilter({
attribute: 'zig',
value: 'zag'
}));
t.ok(f);
t.ok(f.matches({ foo: 'bar', zig: 'zag' }));
t.end();
});
test('match false', function(t) {
var f = new AndFilter();
f.addFilter(new EqualityFilter({
attribute: 'foo',
value: 'bar'
}));
f.addFilter(new EqualityFilter({
attribute: 'zig',
value: 'zag'
}));
t.ok(f);
t.ok(!f.matches({ foo: 'bar', zig: 'zonk' }));
t.end();
});

View File

@ -0,0 +1,98 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var ApproximateFilter;
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- Tests
test('load library', function(t) {
var filters = require('../../lib/index').filters;
t.ok(filters);
ApproximateFilter = filters.ApproximateFilter;
t.ok(ApproximateFilter);
t.end();
});
test('Construct no args', function(t) {
var f = new ApproximateFilter();
t.ok(f);
t.ok(!f.attribute);
t.ok(!f.value);
t.end();
});
test('Construct args', function(t) {
var f = new ApproximateFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.equal(f.attribute, 'foo');
t.equal(f.value, 'bar');
t.equal(f.toString(), '(foo~=bar)');
t.end();
});
test('match true', function(t) {
var f = new ApproximateFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(f.matches({ foo: 'bar' }));
t.end();
});
test('match false', function(t) {
var f = new ApproximateFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(!f.matches({ foo: 'baz' }));
t.end();
});
test('parse ok', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
writer.writeString('bar');
var f = new ApproximateFilter();
t.ok(f);
t.ok(f.parse(new BerReader(writer.buffer)));
t.ok(f.matches({ foo: 'bar' }));
t.end();
});
test('parse bad', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
writer.writeInt(20);
var f = new ApproximateFilter();
t.ok(f);
try {
f.parse(new BerReader(writer.buffer));
t.fail('Should have thrown InvalidAsn1Error');
} catch (e) {
t.equal(e.name, 'InvalidAsn1Error');
}
t.end();
});

98
tst/filters/eq.test.js Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var EqualityFilter;
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- Tests
test('load library', function(t) {
var filters = require('../../lib/index').filters;
t.ok(filters);
EqualityFilter = filters.EqualityFilter;
t.ok(EqualityFilter);
t.end();
});
test('Construct no args', function(t) {
var f = new EqualityFilter();
t.ok(f);
t.ok(!f.attribute);
t.ok(!f.value);
t.end();
});
test('Construct args', function(t) {
var f = new EqualityFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.equal(f.attribute, 'foo');
t.equal(f.value, 'bar');
t.equal(f.toString(), '(foo=bar)');
t.end();
});
test('match true', function(t) {
var f = new EqualityFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(f.matches({ foo: 'bar' }));
t.end();
});
test('match false', function(t) {
var f = new EqualityFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(!f.matches({ foo: 'baz' }));
t.end();
});
test('parse ok', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
writer.writeString('bar');
var f = new EqualityFilter();
t.ok(f);
t.ok(f.parse(new BerReader(writer.buffer)));
t.ok(f.matches({ foo: 'bar' }));
t.end();
});
test('parse bad', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
writer.writeInt(20);
var f = new EqualityFilter();
t.ok(f);
try {
f.parse(new BerReader(writer.buffer));
t.fail('Should have thrown InvalidAsn1Error');
} catch (e) {
t.equal(e.name, 'InvalidAsn1Error');
}
t.end();
});

98
tst/filters/ge.test.js Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var GreaterThanEqualsFilter;
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- Tests
test('load library', function(t) {
var filters = require('../../lib/index').filters;
t.ok(filters);
GreaterThanEqualsFilter = filters.GreaterThanEqualsFilter;
t.ok(GreaterThanEqualsFilter);
t.end();
});
test('Construct no args', function(t) {
var f = new GreaterThanEqualsFilter();
t.ok(f);
t.ok(!f.attribute);
t.ok(!f.value);
t.end();
});
test('Construct args', function(t) {
var f = new GreaterThanEqualsFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.equal(f.attribute, 'foo');
t.equal(f.value, 'bar');
t.equal(f.toString(), '(foo>=bar)');
t.end();
});
test('match true', function(t) {
var f = new GreaterThanEqualsFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(f.matches({ foo: 'baz' }));
t.end();
});
test('match false', function(t) {
var f = new GreaterThanEqualsFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(!f.matches({ foo: 'abc' }));
t.end();
});
test('parse ok', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
writer.writeString('bar');
var f = new GreaterThanEqualsFilter();
t.ok(f);
t.ok(f.parse(new BerReader(writer.buffer)));
t.ok(f.matches({ foo: 'bar' }));
t.end();
});
test('parse bad', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
writer.writeInt(20);
var f = new GreaterThanEqualsFilter();
t.ok(f);
try {
f.parse(new BerReader(writer.buffer));
t.fail('Should have thrown InvalidAsn1Error');
} catch (e) {
t.equal(e.name, 'InvalidAsn1Error');
}
t.end();
});

98
tst/filters/le.test.js Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var LessThanEqualsFilter;
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- Tests
test('load library', function(t) {
var filters = require('../../lib/index').filters;
t.ok(filters);
LessThanEqualsFilter = filters.LessThanEqualsFilter;
t.ok(LessThanEqualsFilter);
t.end();
});
test('Construct no args', function(t) {
var f = new LessThanEqualsFilter();
t.ok(f);
t.ok(!f.attribute);
t.ok(!f.value);
t.end();
});
test('Construct args', function(t) {
var f = new LessThanEqualsFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.equal(f.attribute, 'foo');
t.equal(f.value, 'bar');
t.equal(f.toString(), '(foo<=bar)');
t.end();
});
test('match true', function(t) {
var f = new LessThanEqualsFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(f.matches({ foo: 'abc' }));
t.end();
});
test('match false', function(t) {
var f = new LessThanEqualsFilter({
attribute: 'foo',
value: 'bar'
});
t.ok(f);
t.ok(!f.matches({ foo: 'baz' }));
t.end();
});
test('parse ok', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
writer.writeString('bar');
var f = new LessThanEqualsFilter();
t.ok(f);
t.ok(f.parse(new BerReader(writer.buffer)));
t.ok(f.matches({ foo: 'bar' }));
t.end();
});
test('parse bad', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
writer.writeInt(20);
var f = new LessThanEqualsFilter();
t.ok(f);
try {
f.parse(new BerReader(writer.buffer));
t.fail('Should have thrown InvalidAsn1Error');
} catch (e) {
t.equal(e.name, 'InvalidAsn1Error');
}
t.end();
});

77
tst/filters/not.test.js Normal file
View File

@ -0,0 +1,77 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var EqualityFilter;
var NotFilter;
///--- Tests
test('load library', function(t) {
var filters = require('../../lib/index').filters;
t.ok(filters);
EqualityFilter = filters.EqualityFilter;
NotFilter = filters.NotFilter;
t.ok(EqualityFilter);
t.ok(NotFilter);
t.end();
});
test('Construct no args', function(t) {
try {
new NotFilter();
t.fail('should have thrown');
} catch (e) {
t.ok(e instanceof TypeError);
}
t.end();
});
test('Construct args', function(t) {
var f = new NotFilter({
filter: new EqualityFilter({
attribute: 'foo',
value: 'bar'
})
});
t.ok(f);
t.equal(f.toString(), '(!(foo=bar))');
t.end();
});
test('match true', function(t) {
var f = new NotFilter({
filter: new EqualityFilter({
attribute: 'foo',
value: 'bar'
})
});
t.ok(f);
t.ok(f.matches({ foo: 'baz' }));
t.end();
});
test('match false', function(t) {
var f = new NotFilter({
filter: new EqualityFilter({
attribute: 'foo',
value: 'bar'
})
});
t.ok(f);
t.ok(!f.matches({ foo: 'bar' }));
t.end();
});

81
tst/filters/or.test.js Normal file
View File

@ -0,0 +1,81 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var EqualityFilter;
var OrFilter;
///--- Tests
test('load library', function(t) {
var filters = require('../../lib/index').filters;
t.ok(filters);
EqualityFilter = filters.EqualityFilter;
OrFilter = filters.OrFilter;
t.ok(EqualityFilter);
t.ok(OrFilter);
t.end();
});
test('Construct no args', function(t) {
t.ok(new OrFilter());
t.end();
});
test('Construct args', function(t) {
var f = new OrFilter();
f.addFilter(new EqualityFilter({
attribute: 'foo',
value: 'bar'
}));
f.addFilter(new EqualityFilter({
attribute: 'zig',
value: 'zag'
}));
t.ok(f);
t.equal(f.toString(), '(|(foo=bar)(zig=zag))');
t.end();
});
test('match true', function(t) {
var f = new OrFilter();
f.addFilter(new EqualityFilter({
attribute: 'foo',
value: 'bar'
}));
f.addFilter(new EqualityFilter({
attribute: 'zig',
value: 'zag'
}));
t.ok(f);
t.ok(f.matches({ foo: 'bar', zig: 'zonk' }));
t.end();
});
test('match false', function(t) {
var f = new OrFilter();
f.addFilter(new EqualityFilter({
attribute: 'foo',
value: 'bar'
}));
f.addFilter(new EqualityFilter({
attribute: 'zig',
value: 'zag'
}));
t.ok(f);
t.ok(!f.matches({ foo: 'baz', zig: 'zonk' }));
t.end();
});

View File

@ -0,0 +1,91 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var PresenceFilter;
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- Tests
test('load library', function(t) {
var filters = require('../../lib/index').filters;
t.ok(filters);
PresenceFilter = filters.PresenceFilter;
t.ok(PresenceFilter);
t.end();
});
test('Construct no args', function(t) {
var f = new PresenceFilter();
t.ok(f);
t.ok(!f.attribute);
t.end();
});
test('Construct args', function(t) {
var f = new PresenceFilter({
attribute: 'foo'
});
t.ok(f);
t.equal(f.attribute, 'foo');
t.equal(f.toString(), '(foo=*)');
t.end();
});
test('match true', function(t) {
var f = new PresenceFilter({
attribute: 'foo'
});
t.ok(f);
t.ok(f.matches({ foo: 'bar' }));
t.end();
});
test('match false', function(t) {
var f = new PresenceFilter({
attribute: 'foo'
});
t.ok(f);
t.ok(!f.matches({ bar: 'foo' }));
t.end();
});
test('parse ok', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
var f = new PresenceFilter();
t.ok(f);
t.ok(f.parse(new BerReader(writer.buffer)));
t.ok(f.matches({ foo: 'bar' }));
t.end();
});
test('parse bad', function(t) {
var writer = new BerWriter();
writer.writeInt(20);
var f = new PresenceFilter();
t.ok(f);
try {
f.parse(new BerReader(writer.buffer));
t.fail('Should have thrown InvalidAsn1Error');
} catch (e) {
t.equal(e.name, 'InvalidAsn1Error');
}
t.end();
});

110
tst/filters/substr.test.js Normal file
View File

@ -0,0 +1,110 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var SubstringFilter;
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- Tests
test('load library', function(t) {
var filters = require('../../lib/index').filters;
t.ok(filters);
SubstringFilter = filters.SubstringFilter;
t.ok(SubstringFilter);
t.end();
});
test('Construct no args', function(t) {
var f = new SubstringFilter();
t.ok(f);
t.ok(!f.attribute);
t.ok(!f.value);
t.end();
});
test('Construct args', function(t) {
var f = new SubstringFilter({
attribute: 'foo',
initial: 'bar',
any: ['zig', 'zag'],
'final': 'baz'
});
t.ok(f);
t.equal(f.attribute, 'foo');
t.equal(f.initial, 'bar');
t.equal(f.any.length, 2);
t.equal(f.any[0], 'zig');
t.equal(f.any[1], 'zag');
t.equal(f['final'], 'baz');
t.equal(f.toString(), '(foo=bar*zig*zag*baz)');
t.end();
});
test('match true', function(t) {
var f = new SubstringFilter({
attribute: 'foo',
initial: 'bar',
any: ['zig', 'zag'],
'final': 'baz'
});
t.ok(f);
t.ok(f.matches({ foo: 'barmoozigbarzagblahbaz' }));
t.end();
});
test('match false', function(t) {
var f = new SubstringFilter({
attribute: 'foo',
initial: 'bar',
foo: ['zig', 'zag'],
'final': 'baz'
});
t.ok(f);
t.ok(!f.matches({ foo: 'bafmoozigbarzagblahbaz' }));
t.end();
});
test('parse ok', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
writer.startSequence();
writer.writeString('bar', 0x80);
writer.writeString('bad', 0x81);
writer.writeString('baz', 0x82);
writer.endSequence();
var f = new SubstringFilter();
t.ok(f);
t.ok(f.parse(new BerReader(writer.buffer)));
t.ok(f.matches({ foo: 'bargoobadgoobaz' }));
t.end();
});
test('parse bad', function(t) {
var writer = new BerWriter();
writer.writeString('foo');
writer.writeInt(20);
var f = new SubstringFilter();
t.ok(f);
try {
f.parse(new BerReader(writer.buffer));
t.fail('Should have thrown InvalidAsn1Error');
} catch (e) {
}
t.end();
});

View File

@ -0,0 +1,115 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var AddRequest;
var Attribute;
var dn;
///--- Tests
test('load library', function(t) {
AddRequest = require('../../lib/index').AddRequest;
Attribute = require('../../lib/index').Attribute;
dn = require('../../lib/index').dn;
t.ok(AddRequest);
t.ok(Attribute);
t.ok(dn);
t.end();
});
test('new no args', function(t) {
t.ok(new AddRequest());
t.end();
});
test('new with args', function(t) {
var 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', function(t) {
var 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();
var 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', function(t) {
var 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);
var 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();
});

View File

@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var AddResponse;
///--- Tests
test('load library', function(t) {
AddResponse = require('../../lib/index').AddResponse;
t.ok(AddResponse);
t.end();
});
test('new no args', function(t) {
t.ok(new AddResponse());
t.end();
});
test('new with args', function(t) {
var 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) {
var ber = new BerWriter();
ber.writeEnumeration(0);
ber.writeString('cn=root');
ber.writeString('foo');
var 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) {
var res = new AddResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
});
t.ok(res);
var 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

@ -0,0 +1,82 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var BindRequest;
var dn;
///--- Tests
test('load library', function(t) {
BindRequest = require('../../lib/index').BindRequest;
dn = require('../../lib/index').dn;
t.ok(BindRequest);
t.ok(dn);
t.end();
});
test('new no args', function(t) {
t.ok(new BindRequest());
t.end();
});
test('new with args', function(t) {
var 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) {
var ber = new BerWriter();
ber.writeInt(3);
ber.writeString('cn=root');
ber.writeString('secret', 0x80);
var req = new BindRequest();
t.ok(req._parse(new BerReader(ber.buffer)));
t.equal(req.version, 3);
t.equal(req.dn, 'cn=root');
t.ok(req.name.constructor);
t.equal(req.name.constructor.name, 'DN');
t.equal(req.credentials, 'secret');
t.end();
});
test('toBer', function(t) {
var req = new BindRequest({
messageID: 123,
version: 3,
name: dn.parse('cn=root'),
credentials: 'secret'
});
t.ok(req);
var 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

@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var BindResponse;
///--- Tests
test('load library', function(t) {
BindResponse = require('../../lib/index').BindResponse;
t.ok(BindResponse);
t.end();
});
test('new no args', function(t) {
t.ok(new BindResponse());
t.end();
});
test('new with args', function(t) {
var 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) {
var ber = new BerWriter();
ber.writeEnumeration(0);
ber.writeString('cn=root');
ber.writeString('foo');
var 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) {
var res = new BindResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
});
t.ok(res);
var 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

@ -0,0 +1,87 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var CompareRequest;
var dn;
///--- Tests
test('load library', function(t) {
CompareRequest = require('../../lib/index').CompareRequest;
dn = require('../../lib/index').dn;
t.ok(CompareRequest);
t.ok(dn);
t.end();
});
test('new no args', function(t) {
t.ok(new CompareRequest());
t.end();
});
test('new with args', function(t) {
var req = new CompareRequest({
entry: dn.parse('cn=foo, o=test'),
attribute: 'sn',
value: 'testy'
});
t.ok(req);
t.equal(req.dn, 'cn=foo, o=test');
t.equal(req.attribute, 'sn');
t.equal(req.value, 'testy');
t.end();
});
test('parse', function(t) {
var ber = new BerWriter();
ber.writeString('cn=foo,o=test');
ber.startSequence();
ber.writeString('sn');
ber.writeString('testy');
ber.endSequence();
var 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) {
var req = new CompareRequest({
messageID: 123,
entry: dn.parse('cn=foo, o=test'),
attribute: 'sn',
value: 'testy'
});
t.ok(req);
var 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

@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var CompareResponse;
///--- Tests
test('load library', function(t) {
CompareResponse = require('../../lib/index').CompareResponse;
t.ok(CompareResponse);
t.end();
});
test('new no args', function(t) {
t.ok(new CompareResponse());
t.end();
});
test('new with args', function(t) {
var 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) {
var ber = new BerWriter();
ber.writeEnumeration(0);
ber.writeString('cn=root');
ber.writeString('foo');
var 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) {
var res = new CompareResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
});
t.ok(res);
var 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

@ -0,0 +1,69 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var DeleteRequest;
var dn;
///--- Tests
test('load library', function(t) {
DeleteRequest = require('../../lib/index').DeleteRequest;
dn = require('../../lib/index').dn;
t.ok(DeleteRequest);
t.end();
});
test('new no args', function(t) {
t.ok(new DeleteRequest());
t.end();
});
test('new with args', function(t) {
var req = new DeleteRequest({
entry: dn.parse('cn=test')
});
t.ok(req);
t.equal(req.dn, 'cn=test');
t.end();
});
test('parse', function(t) {
var ber = new BerWriter();
ber.writeString('cn=test', 0x4a);
var req = new DeleteRequest();
var reader = new BerReader(ber.buffer);
reader.readSequence(0x4a);
t.ok(req.parse(reader.buffer, reader.length));
t.equal(req.dn, 'cn=test');
t.end();
});
test('toBer', function(t) {
var req = new DeleteRequest({
messageID: 123,
entry: dn.parse('cn=test')
});
t.ok(req);
var 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

@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var DeleteResponse;
///--- Tests
test('load library', function(t) {
DeleteResponse = require('../../lib/index').DeleteResponse;
t.ok(DeleteResponse);
t.end();
});
test('new no args', function(t) {
t.ok(new DeleteResponse());
t.end();
});
test('new with args', function(t) {
var 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) {
var ber = new BerWriter();
ber.writeEnumeration(0);
ber.writeString('cn=root');
ber.writeString('foo');
var 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) {
var res = new DeleteResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
});
t.ok(res);
var 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

@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var ExtendedRequest;
var dn;
///--- Tests
test('load library', function(t) {
ExtendedRequest = require('../../lib/index').ExtendedRequest;
dn = require('../../lib/index').dn;
t.ok(ExtendedRequest);
t.ok(dn);
t.end();
});
test('new no args', function(t) {
t.ok(new ExtendedRequest());
t.end();
});
test('new with args', function(t) {
var 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.end();
});
test('parse', function(t) {
var ber = new BerWriter();
ber.writeString('1.2.3.4', 0x80);
ber.writeString('test', 0x81);
var 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.end();
});
test('toBer', function(t) {
var req = new ExtendedRequest({
messageID: 123,
requestName: '1.2.3.4',
requestValue: 'test'
});
t.ok(req);
var 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

@ -0,0 +1,88 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var ExtendedResponse;
///--- Tests
test('load library', function(t) {
ExtendedResponse = require('../../lib/index').ExtendedResponse;
t.ok(ExtendedResponse);
t.end();
});
test('new no args', function(t) {
t.ok(new ExtendedResponse());
t.end();
});
test('new with args', function(t) {
var 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) {
var ber = new BerWriter();
ber.writeEnumeration(0);
ber.writeString('cn=root');
ber.writeString('foo');
ber.writeString('1.2.3.4', 0x8a);
ber.writeString('test', 0x8b);
var 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) {
var res = new ExtendedResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo',
responseName: '1.2.3.4',
responseValue: 'test'
});
t.ok(res);
var 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

@ -0,0 +1,82 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var ModifyDNRequest;
var dn;
///--- Tests
test('load library', function(t) {
ModifyDNRequest = require('../../lib/index').ModifyDNRequest;
dn = require('../../lib/index').dn;
t.ok(ModifyDNRequest);
t.ok(dn);
t.end();
});
test('new no args', function(t) {
t.ok(new ModifyDNRequest());
t.end();
});
test('new with args', function(t) {
var req = new ModifyDNRequest({
entry: dn.parse('cn=foo, o=test'),
newRdn: dn.parse('cn=foo2'),
deleteOldRdn: true
});
t.ok(req);
t.equal(req.dn, 'cn=foo, o=test');
t.equal(req.newRdn.toString(), 'cn=foo2');
t.equal(req.deleteOldRdn, true);
t.end();
});
test('parse', function(t) {
var ber = new BerWriter();
ber.writeString('cn=foo,o=test');
ber.writeString('cn=foo2');
ber.writeBoolean(true);
var req = new ModifyDNRequest();
t.ok(req._parse(new BerReader(ber.buffer)));
t.equal(req.dn, 'cn=foo, o=test');
t.equal(req.newRdn.toString(), 'cn=foo2');
t.equal(req.deleteOldRdn, true);
t.end();
});
test('toBer', function(t) {
var req = new ModifyDNRequest({
messageID: 123,
entry: dn.parse('cn=foo, o=test'),
newRdn: dn.parse('cn=foo2'),
deleteOldRdn: true
});
t.ok(req);
var 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

@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var ModifyDNResponse;
///--- Tests
test('load library', function(t) {
ModifyDNResponse = require('../../lib/index').ModifyDNResponse;
t.ok(ModifyDNResponse);
t.end();
});
test('new no args', function(t) {
t.ok(new ModifyDNResponse());
t.end();
});
test('new with args', function(t) {
var 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) {
var ber = new BerWriter();
ber.writeEnumeration(0);
ber.writeString('cn=root');
ber.writeString('foo');
var 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) {
var res = new ModifyDNResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
});
t.ok(res);
var 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

@ -0,0 +1,114 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var ModifyRequest;
var Attribute;
var Change;
var dn;
///--- Tests
test('load library', function(t) {
ModifyRequest = require('../../lib/index').ModifyRequest;
Attribute = require('../../lib/index').Attribute;
Change = require('../../lib/index').Change;
dn = require('../../lib/index').dn;
t.ok(ModifyRequest);
t.ok(Attribute);
t.ok(Change);
t.ok(dn);
t.end();
});
test('new no args', function(t) {
t.ok(new ModifyRequest());
t.end();
});
test('new with args', function(t) {
var 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, '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) {
var 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();
var req = new ModifyRequest();
t.ok(req._parse(new BerReader(ber.buffer)));
t.equal(req.dn, '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) {
var 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);
var 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

@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var ModifyResponse;
///--- Tests
test('load library', function(t) {
ModifyResponse = require('../../lib/index').ModifyResponse;
t.ok(ModifyResponse);
t.end();
});
test('new no args', function(t) {
t.ok(new ModifyResponse());
t.end();
});
test('new with args', function(t) {
var 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) {
var ber = new BerWriter();
ber.writeEnumeration(0);
ber.writeString('cn=root');
ber.writeString('foo');
var 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) {
var res = new ModifyResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
});
t.ok(res);
var 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

@ -0,0 +1,116 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var SearchEntry;
var Attribute;
var dn;
///--- Tests
test('load library', function(t) {
SearchEntry = require('../../lib/index').SearchEntry;
Attribute = require('../../lib/index').Attribute;
dn = require('../../lib/index').dn;
t.ok(SearchEntry);
t.ok(dn);
t.ok(Attribute);
t.end();
});
test('new no args', function(t) {
t.ok(new SearchEntry());
t.end();
});
test('new with args', function(t) {
var 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, '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) {
var 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();
var 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) {
var 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);
var 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

@ -0,0 +1,120 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var SearchRequest;
var EqualityFilter;
var dn;
///--- Tests
test('load library', function(t) {
SearchRequest = require('../../lib/index').SearchRequest;
EqualityFilter = require('../../lib/index').EqualityFilter;
dn = require('../../lib/index').dn;
t.ok(SearchRequest);
t.ok(EqualityFilter);
t.ok(dn);
t.end();
});
test('new no args', function(t) {
t.ok(new SearchRequest());
t.end();
});
test('new with args', function(t) {
var 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) {
var f = new EqualityFilter({
attribute: 'email',
value: 'foo@bar.com'
});
var ber = new BerWriter();
ber.writeString('cn=foo,o=test');
ber.writeEnumeration(0);
ber.writeEnumeration(0);
ber.writeInt(1);
ber.writeInt(2);
ber.writeBoolean(false);
ber = f.toBer(ber);
var 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) {
var 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);
var ber = new BerReader(req.toBer());
t.ok(ber);
t.equal(ber.readSequence(), 0x30);
t.equal(ber.readInt(), 123);
t.equal(ber.readSequence(), 0x63);
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

@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var SearchResponse;
///--- Tests
test('load library', function(t) {
SearchResponse = require('../../lib/index').SearchResponse;
t.ok(SearchResponse);
t.end();
});
test('new no args', function(t) {
t.ok(new SearchResponse());
t.end();
});
test('new with args', function(t) {
var 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) {
var ber = new BerWriter();
ber.writeEnumeration(0);
ber.writeString('cn=root');
ber.writeString('foo');
var 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) {
var res = new SearchResponse({
messageID: 123,
status: 3,
matchedDN: 'cn=root',
errorMessage: 'foo'
});
t.ok(res);
var 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

@ -0,0 +1,57 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
var asn1 = require('asn1');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var UnbindRequest;
///--- Tests
test('load library', function(t) {
UnbindRequest = require('../../lib/index').UnbindRequest;
t.ok(UnbindRequest);
t.end();
});
test('new no args', function(t) {
t.ok(new UnbindRequest());
t.end();
});
test('new with args', function(t) {
var req = new UnbindRequest({});
t.ok(req);
t.end();
});
test('parse', function(t) {
var ber = new BerWriter();
var req = new UnbindRequest();
t.ok(req._parse(new BerReader(ber.buffer)));
t.end();
});
test('toBer', function(t) {
var req = new UnbindRequest({
messageID: 123
});
t.ok(req);
var ber = new BerReader(req.toBer());
t.ok(ber);
t.equal(ber.readSequence(), 0x30);
t.equal(ber.readInt(), 123);
t.end();
});

73
tst/url.test.js Normal file
View File

@ -0,0 +1,73 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var test = require('tap').test;
///--- Globals
var url;
///--- Tests
test('load library', function(t) {
url = require('../lib/index').url;
t.ok(url);
t.end();
});
test('parse empty', function(t) {
var u = url.parse('ldap:///');
t.equal(u.hostname, 'localhost');
t.equal(u.port, 389);
t.ok(!u.DN);
t.ok(!u.attributes);
t.equal(u.secure, false);
t.end();
});
test('parse hostname', function(t) {
var u = url.parse('ldap://example.com/');
t.equal(u.hostname, 'example.com');
t.equal(u.port, 389);
t.ok(!u.DN);
t.ok(!u.attributes);
t.equal(u.secure, false);
t.end();
});
test('parse host and port', function(t) {
var u = url.parse('ldap://example.com:1389/');
t.equal(u.hostname, 'example.com');
t.equal(u.port, 1389);
t.ok(!u.DN);
t.ok(!u.attributes);
t.equal(u.secure, false);
t.end();
});
test('parse full', function(t) {
var u = url.parse('ldaps://ldap.example.com:1389/dc=example%20,dc=com' +
'?cn,sn?sub?(cn=Babs%20Jensen)');
t.equal(u.secure, true);
t.equal(u.hostname, 'ldap.example.com');
t.equal(u.port, 1389);
t.equal(u.DN, 'dc=example ,dc=com');
t.ok(u.attributes);
t.equal(u.attributes.length, 2);
t.equal(u.attributes[0], 'cn');
t.equal(u.attributes[1], 'sn');
t.equal(u.scope, 'sub');
t.equal(u.filter, '(cn=Babs Jensen)');
t.end();
});