Merge pull request #61 from yunong/master

Added Persistent Search Lib, ldap.url now parses filter as a LDAP object
This commit is contained in:
Mark Cavage 2012-02-24 08:37:43 -08:00
commit 8990874caa
6 changed files with 165 additions and 10 deletions

23
docs/persistent_search.md Normal file
View File

@ -0,0 +1,23 @@
---
titile: Persistent Search Cache API| ldapjs
markdown2extras: wiki-tables
logo-color: green
logo-font-family: google:Aldrich, Verdana, sans-serif
header-font-family: google:Aldrich, Verdana, sans-serif
---
# ldapjs Persistent Search Cache API
This document covers the ldapjs Persistent Search Cache API and assumes you are familiar with LDAP. If you're not, read the [guide](http://ldapjs.org/guide.html) first.
This document also assumes you are familiar with LDAP persistent search. If you're not, read the [rfc](http://tools.ietf.org/id/draft-ietf-ldapext-psearch-03.txt) first.
Note this API is a cache used to store all connected persistent search clients, and does not actually implement persistent search.
# addClient(req, res, callback)
Adds a client to the cache.
# removeClient(req, res, callback)
Removes a client from the cache.

View File

@ -52,7 +52,7 @@ var MAX_MSGID = Math.pow(2, 31) - 1;
function xor() { function xor() {
var b = false; var b = false;
for (var i = 0; i < arguments.length; i++) { for (var i = 0; i < arguments.length; i++) {
if (arguments[i] && !b) { if (arguments[i] && !b) {
b = true; b = true;
} else if (arguments[i] && b) { } else if (arguments[i] && b) {
return false; return false;
@ -119,20 +119,21 @@ function Client(options) {
EventEmitter.call(this, options); EventEmitter.call(this, options);
var parsedUrl;
if (options.url) if (options.url)
options.url = url.parse(options.url); parsedUrl = url.parse(options.url);
this.connection = null; this.connection = null;
this.connectTimeout = options.connectTimeout || false; this.connectTimeout = options.connectTimeout || false;
this.connectOptions = { this.connectOptions = {
port: options.url ? options.url.port : options.socketPath, port: parsedUrl ? parsedUrl.port : options.socketPath,
host: options.url ? options.url.hostname : undefined, host: parsedUrl ? parsedUrl.hostname : undefined,
socketPath: options.socketPath || undefined socketPath: options.socketPath || undefined
}; };
this.log = options.log; this.log = options.log;
this.secure = options.url ? options.url.secure : false; this.secure = parsedUrl ? parsedUrl.secure : false;
this.timeout = options.timeout || false; this.timeout = options.timeout || false;
this.url = options.url || false; this.url = parsedUrl || false;
// We'll emit a connect event when this is done // We'll emit a connect event when this is done
this.connect(); this.connect();
@ -753,7 +754,7 @@ Client.prototype.connect = function connect(callback) {
}); });
c.ldap.parser.on('error', function (err) { c.ldap.parser.on('error', function (err) {
log.debug({err: err}, '%s parser error event', c.ldap.id); log.debug({err: err}, '%s parser error event', c.ldap.id, err);
if (self.listeners('error').length) if (self.listeners('error').length)
self.emit('error', err); self.emit('error', err);

View File

@ -10,6 +10,7 @@ var Server = require('./server');
var assert = require('assert'); var assert = require('assert');
var controls = require('./controls'); var controls = require('./controls');
var persistentSearch = require('./persistent_search');
var dn = require('./dn'); var dn = require('./dn');
var errors = require('./errors'); var errors = require('./errors');
var filters = require('./filters'); var filters = require('./filters');
@ -79,14 +80,19 @@ module.exports = {
Change: Change, Change: Change,
DN: dn.DN, DN: dn.DN,
PersistentSearchCache: persistentSearch.PersistentSearchCache,
RDN: dn.RDN, RDN: dn.RDN,
parseDN: dn.parse, parseDN: dn.parse,
dn: dn, dn: dn,
persistentSearch: persistentSearch,
filters: filters, filters: filters,
parseFilter: filters.parseString, parseFilter: filters.parseString,
parseURL: url.parse, parseURL: url.parse,
url: url url: url
}; };

123
lib/persistent_search.js Normal file
View File

@ -0,0 +1,123 @@
///--- Globals
var parseDN = require('./dn').parse;
var EntryChangeNotificationControl =
require('./controls').EntryChangeNotificationControl;
///--- API
// Cache used to store connected persistent search clients
function PersistentSearch() {
this.clientList = [];
}
PersistentSearch.prototype.addClient = function (req, res, callback) {
if (typeof (req) !== 'object')
throw new TypeError('req must be an object');
if (typeof (res) !== 'object')
throw new TypeError('res must be an object');
if (callback && typeof (callback) !== 'function')
throw new TypeError('callback must be a function');
var log = req.log;
var client = {};
client.req = req;
client.res = res;
log.debug('%s storing client', req.logId);
this.clientList.push(client);
log.debug('%s stored client', req.logId);
log.debug('%s total number of clients %s',
req.logId, this.clientList.length);
if (callback)
callback(client);
};
PersistentSearch.prototype.removeClient = function (req, res, callback) {
if (typeof (req) !== 'object')
throw new TypeError('req must be an object');
if (typeof (res) !== 'object')
throw new TypeError('res must be an object');
if (callback && typeof (callback) !== 'function')
throw new TypeError('callback must be a function');
var log = req.log;
log.debug('%s removing client', req.logId);
var client = {};
client.req = req;
client.res = res;
// remove the client if it exists
this.clientList.forEach(function (element, index, array) {
if (element.req === client.req) {
log.debug('%s removing client from list', req.logId);
array.splice(index, 1);
}
});
log.debug('%s number of persistent search clients %s',
req.logId, this.clientList.length);
if (callback)
callback(client);
};
function getOperationType(requestType) {
switch (requestType) {
case 'AddRequest':
case 'add':
return 1;
case 'DeleteRequest':
case 'delete':
return 2;
case 'ModifyRequest':
case 'modify':
return 4;
case 'ModifyDNRequest':
case 'modrdn':
return 8;
default:
throw new TypeError('requestType %s, is an invalid request type',
request);
}
}
function getEntryChangeNotificationControl(req, obj, callback) {
// if we want to return a ECNC
if (req.persistentSearch.value.returnECs) {
var attrs = obj.attributes;
var value = {};
value.changeType = getOperationType(attrs.changetype);
// if it's a modDN request, fill in the previous DN
if (value.changeType === 8 && attrs.previousDN) {
value.previousDN = attrs.previousDN;
}
value.changeNumber = attrs.changenumber;
return new EntryChangeNotificationControl({ value: value });
} else {
return false;
}
}
function checkChangeType(req, requestType) {
return (req.persistentSearch.value.changeTypes &
getOperationType(requestType));
}
///--- Exports
module.exports = {
PersistentSearchCache: PersistentSearch,
checkChangeType: checkChangeType,
getEntryChangeNotificationControl: getEntryChangeNotificationControl
};

View File

@ -5,7 +5,7 @@ var url = require('url');
var util = require('util'); var util = require('util');
var dn = require('./dn'); var dn = require('./dn');
var filter = require('./filters/index');
module.exports = { module.exports = {
@ -55,7 +55,9 @@ module.exports = {
if (!u.scope) if (!u.scope)
u.scope = 'base'; u.scope = 'base';
if (!u.filter) if (!u.filter)
u.filter = '(objectclass=*)'; u.filter = filter.parseString('(objectclass=*)');
else
u.filter = filter.parseString(u.filter);
} }
return u; return u;

View File

@ -68,7 +68,7 @@ test('parse full', function (t) {
t.equal(u.attributes[0], 'cn'); t.equal(u.attributes[0], 'cn');
t.equal(u.attributes[1], 'sn'); t.equal(u.attributes[1], 'sn');
t.equal(u.scope, 'sub'); t.equal(u.scope, 'sub');
t.equal(u.filter, '(cn=Babs Jensen)'); t.equal(u.filter.toString(), '(cn=Babs Jensen)');
t.end(); t.end();
}); });