lots of fixes. committing the last few days of work. (my commit messages are useless right now)

This commit is contained in:
Mark Cavage 2011-08-12 16:37:47 -07:00
parent add3e1a0d6
commit dda17ef190
15 changed files with 214 additions and 55 deletions

View File

@ -22,10 +22,11 @@ function Change(options) {
var self = this; var self = this;
this.__defineGetter__('operation', function() { this.__defineGetter__('operation', function() {
switch (self._operation) { switch (self._operation) {
case 0x00: return 'Add'; case 0x00: return 'add';
case 0x01: return 'Delete'; case 0x01: return 'delete';
case 0x02: return 'Replace'; case 0x02: return 'replace';
default: return 'Invalid'; default:
throw new Error('0x' + self._operation.toString(16) + ' is invalid');
} }
}); });
this.__defineSetter__('operation', function(val) { this.__defineSetter__('operation', function(val) {

View File

@ -593,8 +593,14 @@ Client.prototype.search = function(base, options, controls, callback) {
} else if (typeof(options) !== 'object') { } else if (typeof(options) !== 'object') {
throw new TypeError('options (object) required'); throw new TypeError('options (object) required');
} }
if (!(options.filter instanceof Filter)) if (typeof(options.filter) === 'string') {
options.filter = filters.parseString(options.filter);
} else if (!options.filter) {
options.filter = new PresenceFilter({attribute: 'objectclass'});
} else if (!(options.filter instanceof Filter)) {
throw new TypeError('options.filter (Filter) required'); throw new TypeError('options.filter (Filter) required');
}
if (typeof(controls) === 'function') { if (typeof(controls) === 'function') {
callback = controls; callback = controls;
controls = []; controls = [];

View File

@ -78,7 +78,7 @@ SubstringFilter.prototype.matches = function(target) {
return matcher.test(target[this.attribute]); return matcher.test(target[this.attribute]);
} }
return true; return false;
}; };

View File

@ -104,7 +104,7 @@ AddRequest.prototype.indexOf = function(attr) {
throw new TypeError('attr (string) required'); throw new TypeError('attr (string) required');
for (var i = 0; i < this.attributes.length; i++) for (var i = 0; i < this.attributes.length; i++)
if (req.attributes[i].type === attr) if (this.attributes[i].type === attr)
return i; return i;
return -1; return -1;
@ -133,3 +133,51 @@ AddRequest.prototype.getAttribute = function(name) {
return null; return null;
}; };
AddRequest.prototype.addAttribute = function(attr) {
if (!(attr instanceof Attribute))
throw new TypeEroror('attribute (Attribute) required');
return this.attributes.push(attr);
};
/**
* Returns a "pure" JS representation of this object.
*
* An example object would look like:
*
* {
* "dn": "cn=unit, dc=test",
* "attributes": {
* "cn": ["unit", "foo"],
* "objectclass": ["top", "person"]
* }
* }
*
* @return {Object} that looks like the above.
*/
AddRequest.prototype.toObject = function() {
var self = this;
var obj = {
dn: self.entry ? self.entry.toString() : '',
attributes: {}
};
if (!this.attributes || !this.attributes.length)
return obj;
this.attributes.forEach(function(a) {
if (!obj.attributes[a.type])
obj.attributes[a.type] = [];
a.vals.forEach(function(v) {
if (obj.attributes[a.type].indexOf(v) === -1)
obj.attributes[a.type].push(v);
});
});
return obj;
};

View File

@ -16,8 +16,8 @@ var Protocol = require('../protocol');
var Ber = asn1.Ber; var Ber = asn1.Ber;
var LDAP_BIND_SIMPLE = 'Simple'; var LDAP_BIND_SIMPLE = 'simple';
var LDAP_BIND_SASL = 'Sasl'; var LDAP_BIND_SASL = 'sasl';

View File

@ -51,7 +51,7 @@ CompareRequest.prototype._parse = function(ber) {
this.entry = dn.parse(ber.readString()); this.entry = dn.parse(ber.readString());
ber.readSequence(); ber.readSequence();
this.attribute = ber.readString(); this.attribute = ber.readString().toLowerCase();
this.value = ber.readString(); this.value = ber.readString();
return true; return true;

View File

@ -37,9 +37,7 @@ function DeleteRequest(options) {
var self = this; var self = this;
this.__defineGetter__('type', function() { return 'DeleteRequest'; }); this.__defineGetter__('type', function() { return 'DeleteRequest'; });
this.__defineGetter__('_dn', function() { this.__defineGetter__('_dn', function() { return self.entry; });
return self.entry ? self.entry.toString() : '';
});
} }
util.inherits(DeleteRequest, LDAPMessage); util.inherits(DeleteRequest, LDAPMessage);
module.exports = DeleteRequest; module.exports = DeleteRequest;
@ -48,11 +46,8 @@ module.exports = DeleteRequest;
DeleteRequest.prototype._parse = function(ber, length) { DeleteRequest.prototype._parse = function(ber, length) {
assert.ok(ber); assert.ok(ber);
// What a hack; LDAP is so annoying with its decisions of what to this.entry = dn.parse(ber.buffer.slice(0, length).toString('utf8'));
// shortcut, so this is totally a hack to work around the way the delete ber._offset += ber.length;
// message is structured
this.entry = dn.parse(ber.buffer.slice(0, length).toString());
ber._offset += length;
return true; return true;
}; };

View File

@ -40,9 +40,7 @@ function ModifyRequest(options) {
var self = this; var self = this;
this.__defineGetter__('type', function() { return 'ModifyRequest'; }); this.__defineGetter__('type', function() { return 'ModifyRequest'; });
this.__defineGetter__('_dn', function() { this.__defineGetter__('_dn', function() { return self.object; });
return self.object ? self.object.toString() : '';
});
} }
util.inherits(ModifyRequest, LDAPMessage); util.inherits(ModifyRequest, LDAPMessage);
module.exports = ModifyRequest; module.exports = ModifyRequest;

View File

@ -63,6 +63,38 @@ SearchEntry.prototype.addAttribute = function(attr) {
}; };
SearchEntry.prototype.fromObject = function(obj) {
if (typeof(obj) !== 'object')
throw new TypeError('object required');
var self = this;
if (obj.attributes)
obj = obj.attributes;
this.attributes = [];
Object.keys(obj).forEach(function(k) {
var attr = new Attribute({type: k, vals: []});
if (Array.isArray(obj[k])) {
obj[k].forEach(function(v) {
if (typeof(v) !== 'string')
throw new TypeError(k + ' -> ' + v + ' is not a string');
attr.vals.push(v);
});
} else if (typeof(obj[k]) === 'string') {
attr.vals.push(obj[k]);
} else {
throw new TypeError(k + ' -> ' + obj[k] + ' is not a string');
}
self.attributes.push(attr);
});
return true;
};
SearchEntry.prototype.setAttributes = function(obj) { SearchEntry.prototype.setAttributes = function(obj) {
if (typeof(obj) !== 'object') if (typeof(obj) !== 'object')
throw new TypeError('object required'); throw new TypeError('object required');

View File

@ -6,6 +6,7 @@ var util = require('util');
var LDAPResult = require('./result'); var LDAPResult = require('./result');
var SearchEntry = require('./search_entry'); var SearchEntry = require('./search_entry');
var parseDN = require('../dn').parse;
var Protocol = require('../protocol'); var Protocol = require('../protocol');
@ -20,6 +21,9 @@ function SearchResponse(options) {
options.protocolOp = Protocol.LDAP_REP_SEARCH; options.protocolOp = Protocol.LDAP_REP_SEARCH;
LDAPResult.call(this, options); LDAPResult.call(this, options);
this.attributes = options.attributes ? options.attributes.slice() : [];
this.notAttributes = [];
} }
util.inherits(SearchResponse, LDAPResult); util.inherits(SearchResponse, LDAPResult);
module.exports = SearchResponse; module.exports = SearchResponse;
@ -31,30 +35,67 @@ module.exports = SearchResponse;
* @param {Object} entry an instance of SearchEntry. * @param {Object} entry an instance of SearchEntry.
*/ */
SearchResponse.prototype.send = function(entry) { SearchResponse.prototype.send = function(entry) {
if (!entry || !(entry instanceof SearchEntry)) if (!entry || typeof(entry) !== 'object')
throw new TypeError('entry (SearchEntry) required'); throw new TypeError('entry (SearchEntry) required');
if (entry.messageID !== this.messageID)
throw new Error('SearchEntry messageID mismatch');
assert.ok(this.connection); var self = this;
if (this.log.isDebugEnabled()) if (!(entry instanceof SearchEntry)) {
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.json); if (!entry.dn)
throw new Error('entry.dn required');
if (!entry.attributes)
throw new Error('entry.attributes required');
this.connection.write(entry.toBer()); var save = entry;
};
// Rip out anything that either the client didn't ask for, the server
// wants to strip, or 'private' vars that are prefixed with '_'
Object.keys(entry.attributes).forEach(function(a) {
if ((self.attributes.length && self.attributes.indexOf(a) === -1) ||
(self.notAttributes.length && self.notAttributes.indexOf(a) !== -1) ||
(a.length && a.charAt(0) === '_')) {
delete entry.attributes[a];
}
});
SearchResponse.prototype.createSearchEntry = function(options) { entry = new SearchEntry({
if (options) { objectName: typeof(save.dn) === 'string' ? parseDN(save.dn) : save.dn,
if (typeof(options) !== 'object') messageID: self.messageID,
throw new TypeError('options must be an object'); log4js: self.log4js
});
entry.fromObject(save);
} else { } else {
options = {}; if (!entry.messageID)
entry.messageID = this.messageID;
if (entry.messageID !== this.messageID)
throw new Error('SearchEntry messageID mismatch');
} }
options.messageID = this.messageID; try {
options.log4js = this.log4js; if (this.log.isDebugEnabled())
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.json);
this.connection.write(entry.toBer());
} catch (e) {
this.log.warn('%s failure to write message %j: %s',
this.connection.ldap.id, this.json, e.toString());
}
return new SearchEntry(options); };
SearchResponse.prototype.createSearchEntry = function(object) {
if (!object || typeof(object) !== 'object')
throw new TypeError('object required');
var self = this;
var entry = new SearchEntry({
messageID: self.messageID,
log4js: self.log4js,
objectName: object.objectName || object.dn
});
entry.fromObject((object.attributes || object));
return entry;
}; };

View File

@ -22,6 +22,7 @@ var DeleteResponse = require('./messages/del_response');
var ExtendedResponse = require('./messages/ext_response'); var ExtendedResponse = require('./messages/ext_response');
var ModifyResponse = require('./messages/modify_response'); var ModifyResponse = require('./messages/modify_response');
var ModifyDNResponse = require('./messages/moddn_response'); var ModifyDNResponse = require('./messages/moddn_response');
var SearchRequest = require('./messages/search_request');
var SearchResponse = require('./messages/search_response'); var SearchResponse = require('./messages/search_response');
var UnbindResponse = require('./messages/unbind_response'); var UnbindResponse = require('./messages/unbind_response');
@ -108,7 +109,8 @@ function getResponse(req) {
var res = new Response({ var res = new Response({
messageID: req.messageID, messageID: req.messageID,
log4js: req.log4js log4js: req.log4js,
attributes: ((req instanceof SearchRequest) ? req.attributes : undefined)
}); });
res.connection = req.connection; res.connection = req.connection;
res.logId = req.logId; res.logId = req.logId;
@ -203,11 +205,17 @@ function Server(options) {
EventEmitter.call(this, options); EventEmitter.call(this, options);
var log = this.log = options.log4js.getLogger('LDAPServer'); this.log = options.log4js.getLogger('LDAPServer');
var log = this.log;
function setupConnection(c) { function setupConnection(c) {
assert.ok(c); assert.ok(c);
if (c.type === 'unix') {
c.remoteAddress = self.server.path;
c.remotePort = c.fd;
}
c.ldap = { c.ldap = {
id: c.remoteAddress + ':' + c.remotePort, id: c.remoteAddress + ':' + c.remotePort,
config: options config: options
@ -242,11 +250,6 @@ function Server(options) {
} }
function newConnection(c) { function newConnection(c) {
if (c.type === 'unix') {
c.remoteAddress = self.server.path;
c.remotePort = c.fd;
}
setupConnection(c); setupConnection(c);
if (log.isTraceEnabled()) if (log.isTraceEnabled())
log.trace('new connection from %s', c.ldap.id); log.trace('new connection from %s', c.ldap.id);
@ -256,7 +259,7 @@ function Server(options) {
}); });
c.parser.on('message', function(req) { c.parser.on('message', function(req) {
req.connection = c; req.connection = c;
req.logId = c.remoteAddress + '::' + req.messageID; req.logId = c.ldap.id + '::' + req.messageID;
if (log.isDebugEnabled()) if (log.isDebugEnabled())
log.debug('%s: message received: req=%j', c.ldap.id, req.json); log.debug('%s: message received: req=%j', c.ldap.id, req.json);
@ -713,11 +716,17 @@ Server.prototype._getHandlerChain = function(req) {
handlers: route[op] || [defaultExopHandler] handlers: route[op] || [defaultExopHandler]
}; };
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) { } else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
function getUnbindChain() {
if (routes['unbind'] && routes['unbind'][op])
return routes['unbind'][op];
self.log.debug('%s unbind request %j', req.logId, req.json);
return [defaultUnbindHandler];
}
return { return {
backend: routes['unbind'] ? routes['unbind'].backend : self, backend: routes['unbind'] ? routes['unbind'].backend : self,
handlers: (routes['unbind'] && routes['unbind'][op] ? handlers: getUnbindChain()
routes['unbind'][op] :
[defaultUnbindHandler])
}; };
} }

View File

@ -40,7 +40,7 @@ test('new with args', function(t) {
}); });
t.ok(change); t.ok(change);
t.equal(change.operation, 'Add'); t.equal(change.operation, 'add');
t.equal(change.modification.type, 'cn'); t.equal(change.modification.type, 'cn');
t.equal(change.modification.vals.length, 2); t.equal(change.modification.vals.length, 2);
t.equal(change.modification.vals[0], 'foo'); t.equal(change.modification.vals[0], 'foo');
@ -90,7 +90,7 @@ test('parse', function(t) {
t.ok(change); t.ok(change);
t.ok(change.parse(new BerReader(ber.buffer))); t.ok(change.parse(new BerReader(ber.buffer)));
t.equal(change.operation, 'Add'); t.equal(change.operation, 'add');
t.equal(change.modification.type, 'cn'); t.equal(change.modification.type, 'cn');
t.equal(change.modification.vals.length, 2); t.equal(change.modification.vals.length, 2);
t.equal(change.modification.vals[0], 'foo'); t.equal(change.modification.vals[0], 'foo');

View File

@ -113,3 +113,32 @@ test('toBer', function(t) {
t.end(); t.end();
}); });
test('toObject', function(t) {
var req = new AddRequest({
entry: dn.parse('cn=foo, o=test'),
attributes: [new Attribute({type: 'cn', vals: ['foo', 'bar']}),
new Attribute({type: 'objectclass', vals: ['person']})]
});
t.ok(req);
var obj = req.toObject();
t.ok(obj);
t.ok(obj.dn);
t.equal(obj.dn, 'cn=foo, o=test');
t.ok(obj.attributes);
t.ok(obj.attributes.cn);
t.ok(Array.isArray(obj.attributes.cn));
t.equal(obj.attributes.cn.length, 2);
t.equal(obj.attributes.cn[0], 'foo');
t.equal(obj.attributes.cn[1], 'bar');
t.ok(obj.attributes.objectclass);
t.ok(Array.isArray(obj.attributes.objectclass));
t.equal(obj.attributes.objectclass.length, 1);
t.equal(obj.attributes.objectclass[0], 'person');
t.end();
});

View File

@ -34,7 +34,7 @@ test('new with args', function(t) {
entry: dn.parse('cn=test') entry: dn.parse('cn=test')
}); });
t.ok(req); t.ok(req);
t.equal(req.dn, 'cn=test'); t.equal(req.dn.toString(), 'cn=test');
t.end(); t.end();
}); });
@ -47,7 +47,7 @@ test('parse', function(t) {
var reader = new BerReader(ber.buffer); var reader = new BerReader(ber.buffer);
reader.readSequence(0x4a); reader.readSequence(0x4a);
t.ok(req.parse(reader.buffer, reader.length)); t.ok(req.parse(reader.buffer, reader.length));
t.equal(req.dn, 'cn=test'); t.equal(req.dn.toString(), 'cn=test');
t.end(); t.end();
}); });

View File

@ -46,7 +46,7 @@ test('new with args', function(t) {
t.ok(req); t.ok(req);
t.equal(req.dn.toString(), 'cn=foo, o=test'); t.equal(req.dn.toString(), 'cn=foo, o=test');
t.equal(req.changes.length, 1); t.equal(req.changes.length, 1);
t.equal(req.changes[0].operation, 'Replace'); t.equal(req.changes[0].operation, 'replace');
t.equal(req.changes[0].modification.type, 'objectclass'); t.equal(req.changes[0].modification.type, 'objectclass');
t.equal(req.changes[0].modification.vals[0], 'person'); t.equal(req.changes[0].modification.vals[0], 'person');
t.end(); t.end();
@ -74,9 +74,9 @@ test('parse', function(t) {
var req = new ModifyRequest(); var req = new ModifyRequest();
t.ok(req._parse(new BerReader(ber.buffer))); t.ok(req._parse(new BerReader(ber.buffer)));
t.equal(req.dn, 'cn=foo, o=test'); t.equal(req.dn.toString(), 'cn=foo, o=test');
t.equal(req.changes.length, 1); t.equal(req.changes.length, 1);
t.equal(req.changes[0].operation, 'Replace'); t.equal(req.changes[0].operation, 'replace');
t.equal(req.changes[0].modification.type, 'objectclass'); t.equal(req.changes[0].modification.type, 'objectclass');
t.equal(req.changes[0].modification.vals[0], 'person'); t.equal(req.changes[0].modification.vals[0], 'person');
t.end(); t.end();