From f4b49a20ae04289286600277eda2a3ae45c075de Mon Sep 17 00:00:00 2001 From: Mark Cavage Date: Tue, 27 Sep 2011 11:49:33 -0700 Subject: [PATCH] GH-14: Client needs to handle search references --- lib/client.js | 9 +++ lib/messages/index.js | 2 + lib/messages/parser.js | 5 ++ lib/messages/search_reference.js | 106 +++++++++++++++++++++++++++++++ lib/messages/search_response.js | 35 ++++++++-- tst/client.test.js | 55 +++++++++++++--- 6 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 lib/messages/search_reference.js diff --git a/lib/client.js b/lib/client.js index b7cea47..2d6dc70 100644 --- a/lib/client.js +++ b/lib/client.js @@ -33,6 +33,7 @@ var UnbindRequest = messages.UnbindRequest; var LDAPResult = messages.LDAPResult; var SearchEntry = messages.SearchEntry; +var SearchReference = messages.SearchReference; var SearchResponse = messages.SearchResponse; var Parser = messages.Parser; @@ -587,6 +588,7 @@ Client.prototype._send = function(message, expect, callback, connection) { 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]; @@ -602,11 +604,18 @@ Client.prototype._send = function(message, expect, callback, connection) { return callback(null, res); 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 { delete conn.ldap.messages[message.messageID]; diff --git a/lib/messages/index.js b/lib/messages/index.js index cec67e0..4449d31 100644 --- a/lib/messages/index.js +++ b/lib/messages/index.js @@ -20,6 +20,7 @@ var ModifyDNRequest = require('./moddn_request'); var ModifyDNResponse = require('./moddn_response'); var SearchRequest = require('./search_request'); var SearchEntry = require('./search_entry'); +var SearchReference = require('./search_reference'); var SearchResponse = require('./search_response'); var UnbindRequest = require('./unbind_request'); var UnbindResponse = require('./unbind_response'); @@ -50,6 +51,7 @@ module.exports = { ModifyDNResponse: ModifyDNResponse, SearchRequest: SearchRequest, SearchEntry: SearchEntry, + SearchReference: SearchReference, SearchResponse: SearchResponse, UnbindRequest: UnbindRequest, UnbindResponse: UnbindResponse diff --git a/lib/messages/parser.js b/lib/messages/parser.js index b0cf647..172400f 100644 --- a/lib/messages/parser.js +++ b/lib/messages/parser.js @@ -22,6 +22,7 @@ var ModifyDNRequest = require('./moddn_request'); var ModifyDNResponse = require('./moddn_response'); var SearchRequest = require('./search_request'); var SearchEntry = require('./search_entry'); +var SearchReference = require('./search_reference'); var SearchResponse = require('./search_response'); var UnbindRequest = require('./unbind_request'); var UnbindResponse = require('./unbind_response'); @@ -211,6 +212,10 @@ Parser.prototype._newMessage = function(ber) { Message = SearchEntry; break; + case Protocol.LDAP_REP_SEARCH_REF: + Message = SearchReference; + break; + case Protocol.LDAP_REP_SEARCH: Message = SearchResponse; break; diff --git a/lib/messages/search_reference.js b/lib/messages/search_reference.js new file mode 100644 index 0000000..cfa8908 --- /dev/null +++ b/lib/messages/search_reference.js @@ -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 LDAPMessage = require('./message'); +var Protocol = require('../protocol'); +var dn = require('../dn'); +var url = require('../url'); + + + +///--- Globals + +var BerWriter = asn1.BerWriter; +var parseURL = url.parse; + + + +///--- API + +function SearchReference(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'); + } else { + options = {}; + } + + options.protocolOp = Protocol.LDAP_REP_SEARCH_REF; + LDAPMessage.call(this, options); + + this.uris = options.uris || []; + + var self = this; + this.__defineGetter__('type', function() { return 'SearchReference'; }); + this.__defineGetter__('object', function() { + return { + dn: self.dn.toString(), + uris: self.uris.slice() + }; + }); + this.__defineGetter__('_dn', function() { + return new dn.DN(''); + }); + this.__defineGetter__('urls', function() { + return self.uris; + }); + this.__defineSetter__('urls', function(u) { + self.uris = u.slice(); + }); +} +util.inherits(SearchReference, LDAPMessage); +module.exports = SearchReference; + + +SearchReference.prototype.toObject = function() { + return this.object; +}; + + +SearchReference.prototype.fromObject = function(obj) { + if (typeof(obj) !== 'object') + throw new TypeError('object required'); + + this.uris = obj.uris ? obj.uris.slice() : []; + + return true; +}; + +SearchReference.prototype._json = function(j) { + assert.ok(j); + j.uris = this.uris.slice(); + return j; +}; + + +SearchReference.prototype._parse = function(ber, length) { + assert.ok(ber); + + while (ber.offset < length) { + var _url = ber.readString(); + parseURL(_url); + this.uris.push(_url); + } + + return true; +}; + + +SearchReference.prototype._toBer = function(ber) { + assert.ok(ber); + + this.uris.forEach(function(u) { + ber.writeString(u.href || u); + }); + + return ber; +}; + + + diff --git a/lib/messages/search_response.js b/lib/messages/search_response.js index c387276..b1ac48e 100644 --- a/lib/messages/search_response.js +++ b/lib/messages/search_response.js @@ -5,8 +5,10 @@ var util = require('util'); var LDAPResult = require('./result'); var SearchEntry = require('./search_entry'); +var SearchReference = require('./search_reference'); var parseDN = require('../dn').parse; +var parseURL = require('../url').parse; var Protocol = require('../protocol'); @@ -45,7 +47,12 @@ SearchResponse.prototype.send = function(entry, nofiltering) { var self = this; - 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) throw new Error('entry.dn required'); if (!entry.attributes) @@ -72,11 +79,6 @@ SearchResponse.prototype.send = function(entry, nofiltering) { log4js: self.log4js }); entry.fromObject(save); - } else { - if (!entry.messageID) - entry.messageID = this.messageID; - if (entry.messageID !== this.messageID) - throw new Error('SearchEntry messageID mismatch'); } try { @@ -108,3 +110,24 @@ SearchResponse.prototype.createSearchEntry = function(object) { return entry; }; + + +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 + }); +}; diff --git a/tst/client.test.js b/tst/client.test.js index aefb24c..dffae16 100644 --- a/tst/client.test.js +++ b/tst/client.test.js @@ -76,15 +76,21 @@ test('setup', function(t) { }); 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(); return next(); }); @@ -344,6 +350,37 @@ test('search basic', function(t) { }); +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) { client.unbind(function() { server.on('close', function() {