Socket
Socket
Sign inDemoInstall

ldapjs

Package Overview
Dependencies
Maintainers
1
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ldapjs - npm Package Compare versions

Comparing version 0.1.6 to 0.1.7

lib/messages/search_reference.js

41

docs/client.md

@@ -31,18 +31,17 @@ ---

||numConnections||The size of the connection pool. Default is 1.||
||reconnect||Whether or not to automatically reconnect (and rebind) on socket errors. Takes amount of time in millliseconds. Default is 1000. 0/false will disable altogether.||
## Connection management
If you'll recall, the LDAP protocol is connection-oriented, and completely
asynchronous on a connection (meaning you can send as many requests as you want
without waiting for responses). However, our friend `bind` is a little
different in that you generally want to wait for binds to be completed since
subsequent operations assume that level of privilege.
As LDAP is a stateful protocol (as opposed to HTTP), having connections torn
down from underneath you is difficult to deal with. As such, the ldapjs client
will automatically reconnect when the underlying socket has errors. You can
disable this behavior by passing `reconnect=false` in the options at construct
time, or just setting the reconnect property to false at any time.
The ldapjs client deals with this by maintaing a connection pool, and splaying
requests across that connection pool, with the exception of `bind` and `unbind`,
which it will apply to all connections in the pool. By default, a client will
have one connection in the pool (since it's async already, you don't always need
the complexity of a pool). And after that, the operations in the client are
pretty much a mapping of the LDAP C API, but made higher-level, so they make
sense in JS.
On reconnect, the client will additionally automatically rebind (assuming you
ever successfully called bind). Only after the rebind succeeds will other
operations be allowed back through; in the meantime all callbacks will receive
a `DisconnectedError`. If you never called `bind`, the client will allow
operations when the socket is connected.

@@ -211,9 +210,10 @@ ## Common patterns

Responses from the `search` method are an `EventEmitter` where you will get a
notification for each search entry that comes back from the server. You will
additionally be able to listen for an `error` and `end` event. Note that the
`error` event will only be for client/TCP errors, not LDAP error codes like the
other APIs. You'll want to check the LDAP status code (likely for `0`) on the
`end` event to assert success. LDAP search results can give you a lot of status
codes, such as time or size exceeded, busy, inappropriate matching, etc.,
which is why this method doesn't try to wrap up the code matching.
notification for each `searchEntry` that comes back from the server. You will
additionally be able to listen for a `searchReference`, `error` and `end` event.
Note that the `error` event will only be for client/TCP errors, not LDAP error
codes like the other APIs. You'll want to check the LDAP status code
(likely for `0`) on the `end` event to assert success. LDAP search results
can give you a lot of status codes, such as time or size exceeded, busy,
inappropriate matching, etc., which is why this method doesn't try to wrap up
the code matching.

@@ -233,2 +233,5 @@ Example:

});
res.on('searchReference', function(referral) {
console.log('referral: ' + referral.uris.join());
});
res.on('error', function(err) {

@@ -235,0 +238,0 @@ console.error('error: ' + err.message);

@@ -408,3 +408,3 @@ ---

server.delete('o=example', function(req, res, next) {
server.del('o=example', function(req, res, next) {
console.log('DN: ' + req.dn.toString());

@@ -411,0 +411,0 @@ res.end();

@@ -36,2 +36,3 @@ // Copyright 2011 Mark Cavage, Inc. All rights reserved.

var SearchEntry = messages.SearchEntry;
var SearchReference = messages.SearchReference;
var SearchResponse = messages.SearchResponse;

@@ -77,10 +78,14 @@ var Parser = messages.Parser;

function DisconnectedError(message) {
Error.call(this, message);
if (Error.captureStackTrace)
Error.captureStackTrace(this, DisconnectedError);
function ConnectionError(message) {
errors.LDAPError.call(this,
'ConnectionError',
0x80, // LDAP_OTHER,
message,
null,
ConnectionError);
}
util.inherits(DisconnectedError, Error);
util.inherits(ConnectionError, errors.LDAPError);
///--- API

@@ -125,3 +130,4 @@

port: self.url ? self.url.port : options.socketPath,
host: self.url ? self.url.hostname : undefined
host: self.url ? self.url.hostname : undefined,
socketPath: options.socketPath || undefined
};

@@ -137,69 +143,5 @@ this.shutdown = false;

// Build the connection pool
function newConnection() {
var c;
if (self.secure) {
c = tls.connect(self.connectOptions.port, self.connectOptions.host);
} else {
c = net.createConnection(self.connectOptions.port,
self.connectOptions.host);
}
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.fd;
self.emit('connect', c.ldap.id);
});
c.on('end', function() {
self.emit('end');
});
c.addListener('close', function(had_err) {
self.emit('close', had_err);
});
c.on('error', function(err) {
self.emit('error', err);
});
c.on('timeout', function() {
self.emit('timeout');
});
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;
}
self.connection = newConnection();
this.connection = this._newConnection();
this.reconnect = (typeof(options.reconnect) === 'number' ?
options.reconnect : 1000);
}

@@ -217,5 +159,6 @@ util.inherits(Client, EventEmitter);

* @param {Function} callback of the form f(err, res).
* @param {Socket} conn don't use this. Internal only (reconnects).
* @throws {TypeError} on invalid input.
*/
Client.prototype.bind = function(name, credentials, controls, callback) {
Client.prototype.bind = function(name, credentials, controls, callback, conn) {
if (typeof(name) !== 'string')

@@ -243,3 +186,10 @@ throw new TypeError('name (string) required');

return self._send(req, [errors.LDAP_SUCCESS], callback);
return self._send(req, [errors.LDAP_SUCCESS], function(err, res) {
if (!err) { // In case we need to reconnect later
self._bindDN = name;
self._credentials = credentials;
}
return callback(err, res);
}, conn);
};

@@ -590,2 +540,5 @@

if (!this.connection)
return callback(new ConnectionError('no connection'));
var res = new EventEmitter();

@@ -609,8 +562,10 @@ this._send(req, [errors.LDAP_SUCCESS], res);

throw new TypeError('callback must be a function');
if (!callback)
callback = function defUnbindCb() { self.log.trace('disconnected'); };
var self = this;
this.reconnect = false;
this._bindDN = null;
this._credentials = null;
if (!callback)
callback = function defUnbindCb() { self.log.trace('disconnected'); };
var req = new UnbindRequest();

@@ -622,3 +577,3 @@ return self._send(req, 'unbind', callback);

Client.prototype._send = function(message, expect, callback) {
Client.prototype._send = function(message, expect, callback, connection) {
assert.ok(message);

@@ -628,6 +583,12 @@ assert.ok(expect);

var conn = this.connection || connection;
var self = this;
var conn = self.connection;
if (!conn) {
if (typeof(callback) === 'function')
return callback(new ConnectionError('no connection'));
return callback.emit('error', new ConnectionError('no connection'));
}
// Now set up the callback in the messages table

@@ -640,2 +601,3 @@ message.messageID = conn.ldap.nextMessageID;

var err = null;
if (res instanceof LDAPResult) {

@@ -656,5 +618,14 @@ delete conn.ldap.messages[message.messageID];

callback.emit('end', res);
} else if (res instanceof SearchEntry) {
assert.ok(callback instanceof EventEmitter);
callback.emit('searchEntry', res);
} else if (res instanceof SearchReference) {
assert.ok(callback instanceof EventEmitter);
callback.emit('searchReference', res);
} else if (res instanceof Error) {
return callback(res);
} else {

@@ -678,5 +649,2 @@ delete conn.ldap.messages[message.messageID];

return conn.write(message.toBer(), (expect === 'unbind' ? function() {
conn.on('end', function() {
return callback();
});
conn.end();

@@ -686,1 +654,121 @@ } : null));

Client.prototype._newConnection = function() {
var c;
var connectOpts = this.connectOptions;
var log = this.log;
var self = this;
if (this.secure) {
c = tls.connect(connectOpts.port, connectOpts.host);
} else {
c = net.createConnection(connectOpts.port, connectOpts.host);
}
assert.ok(c);
c.parser = new Parser({
log4js: self.log4js
});
// Wrap the events
c.ldap = {
id: connectOpts.socketPath || self.url.hostname,
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.id += ':' + c.fd;
self.emit('connect', c.ldap.id);
if (log.isTraceEnabled())
log.trace('%s connect event', c.ldap.id);
if (self._bindDN && self._credentials) { // reconnect case
self.bind(self._bindDN, self._credentials, [], function(err) {
if(err) {
log.trace('%s error rebinding: %s', c.ldap.id, err.stack);
return c.end();
}
self.connection = c;
}, c);
} else {
self.connection = c;
}
});
c.on('end', function() {
self.emit('end');
if (log.isTraceEnabled())
log.trace('%s end event', c.ldap.id);
});
c.addListener('close', function(had_err) {
self.emit('close', had_err);
if (log.isTraceEnabled())
log.trace('%s close event had_err=%s', c.ldap.id, had_err ? 'yes' : 'no');
Object.keys(c.ldap.messages).forEach(function(msgid) {
if (typeof(c.ldap.messages[msgid]) === 'function') {
var _cb = c.ldap.messages[msgid];
delete c.ldap.messages[msgid];
return _cb(new ConnectionError(c.ldap.id + ' closed'));
} else if (c.ldap.messages[msgid]) {
c.ldap.messages[msgid].emit('error', new ConnectionError(c.ldap.id +
' closed'));
}
delete c.ldap.messages[msgid];
});
delete c.ldap;
if (self.reconnect) {
self.connection = null;
setTimeout(function() { self._newConnection() }, self.reconnect);
}
});
c.on('error', function(err) {
if (self.listeners('error').length)
self.emit('error', err);
if (log.isTraceEnabled())
log.trace('%s error event=%s', c.ldap.id, err ? err.stack : '?');
c.end();
});
c.on('timeout', function() {
self.emit('timeout');
if (log.isTraceEnabled())
log.trace('%s timeout event=%s', c.ldap.id);
c.end();
});
c.on('data', function(data) {
if (log.isTraceEnabled())
log.trace('%s data event: %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) {
log.error('%s: unsolicited message: %j', c.ldap.id, message.json);
return;
}
return callback(message);
});
return c;
}

@@ -83,2 +83,3 @@ // Copyright 2011 Mark Cavage, Inc. All rights reserved.

module.exports = {};
module.exports.LDAPError = LDAPError;

@@ -85,0 +86,0 @@ Object.keys(CODES).forEach(function(code) {

@@ -23,2 +23,3 @@ // Copyright 2011 Mark Cavage, Inc. All rights reserved.

var SearchEntry = require('./search_entry');
var SearchReference = require('./search_reference');
var SearchResponse = require('./search_response');

@@ -54,2 +55,3 @@ var UnbindRequest = require('./unbind_request');

SearchEntry: SearchEntry,
SearchReference: SearchReference,
SearchResponse: SearchResponse,

@@ -56,0 +58,0 @@ UnbindRequest: UnbindRequest,

@@ -25,2 +25,3 @@ // Copyright 2011 Mark Cavage, Inc. All rights reserved.

var SearchEntry = require('./search_entry');
var SearchReference = require('./search_reference');
var SearchResponse = require('./search_response');

@@ -215,2 +216,6 @@ var UnbindRequest = require('./unbind_request');

case Protocol.LDAP_REP_SEARCH_REF:
Message = SearchReference;
break;
case Protocol.LDAP_REP_SEARCH:

@@ -217,0 +222,0 @@ Message = SearchResponse;

@@ -8,4 +8,6 @@ // Copyright 2011 Mark Cavage, Inc. All rights reserved.

var SearchEntry = require('./search_entry');
var SearchReference = require('./search_reference');
var parseDN = require('../dn').parse;
var parseURL = require('../url').parse;
var Protocol = require('../protocol');

@@ -49,3 +51,8 @@

if (!(entry instanceof SearchEntry)) {
if (entry instanceof SearchEntry || entry instanceof SearchReference) {
if (!entry.messageID)
entry.messageID = this.messageID;
if (entry.messageID !== this.messageID)
throw new Error('SearchEntry messageID mismatch');
} else {
if (!entry.dn)

@@ -77,7 +84,2 @@ throw new Error('entry.dn required');

entry.fromObject(save);
} else {
if (!entry.messageID)
entry.messageID = this.messageID;
if (entry.messageID !== this.messageID)
throw new Error('SearchEntry messageID mismatch');
}

@@ -114,1 +116,22 @@

};
SearchResponse.prototype.createSearchReference = function(uris) {
if (!uris)
throw new TypeError('uris ([string]) required');
if (!Array.isArray(uris))
uris = [uris];
for (var i = 0; i < uris.length; i++) {
if (typeof(uris[i]) == 'string')
uris[i] = parseURL(uris[i]);
}
var self = this;
return new SearchReference({
messageID: self.messageID,
log4js: self.log4js,
uris: uris
});
};

@@ -6,3 +6,3 @@ {

"description": "LDAP client and server APIs",
"version": "0.1.6",
"version": "0.1.7",
"repository": {

@@ -9,0 +9,0 @@ "type": "git",

@@ -79,11 +79,17 @@ // Copyright 2011 Mark Cavage, Inc. All rights reserved.

server.search(SUFFIX, function(req, res, next) {
var e = res.createSearchEntry({
objectName: req.dn,
attributes: {
cn: ['unit', 'test'],
sn: 'testy'
}
});
res.send(e);
res.send(e);
if (!req.dn.equals('cn=ref,' + SUFFIX)) {
var e = res.createSearchEntry({
objectName: req.dn,
attributes: {
cn: ['unit', 'test'],
sn: 'testy'
}
});
res.send(e);
res.send(e);
} else {
res.send(res.createSearchReference('ldap://localhost'));
}
res.end();

@@ -100,6 +106,7 @@ return next();

client = ldap.createClient({
socketPath: SOCKET
socketPath: SOCKET,
reconnect: false // turn this off for unit testing
});
t.ok(client);
// client.log4js.setLevel('Debug');
client.log4js.setLevel('Trace');
t.end();

@@ -348,2 +355,33 @@ });

test('search referral', function(t) {
client.search('cn=ref, ' + SUFFIX, '(objectclass=*)', function(err, res) {
t.ifError(err);
t.ok(res);
var gotEntry = 0;
var gotReferral = false;
res.on('searchEntry', function(entry) {
gotEntry++;
});
res.on('searchReference', function(referral) {
gotReferral = true;
t.ok(referral);
t.ok(referral instanceof ldap.SearchReference);
t.ok(referral.uris);
t.ok(referral.uris.length);
});
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, 0);
t.ok(gotReferral);
t.end();
});
});
});
test('shutdown', function(t) {

@@ -350,0 +388,0 @@ client.unbind(function() {

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc