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:
commit
8990874caa
|
@ -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.
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue