lots of fixes. committing the last few days of work. (my commit messages are useless right now)
This commit is contained in:
parent
add3e1a0d6
commit
dda17ef190
|
@ -22,10 +22,11 @@ function Change(options) {
|
|||
var self = this;
|
||||
this.__defineGetter__('operation', function() {
|
||||
switch (self._operation) {
|
||||
case 0x00: return 'Add';
|
||||
case 0x01: return 'Delete';
|
||||
case 0x02: return 'Replace';
|
||||
default: return 'Invalid';
|
||||
case 0x00: return 'add';
|
||||
case 0x01: return 'delete';
|
||||
case 0x02: return 'replace';
|
||||
default:
|
||||
throw new Error('0x' + self._operation.toString(16) + ' is invalid');
|
||||
}
|
||||
});
|
||||
this.__defineSetter__('operation', function(val) {
|
||||
|
|
|
@ -593,8 +593,14 @@ Client.prototype.search = function(base, options, controls, callback) {
|
|||
} else if (typeof(options) !== 'object') {
|
||||
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');
|
||||
}
|
||||
|
||||
if (typeof(controls) === 'function') {
|
||||
callback = controls;
|
||||
controls = [];
|
||||
|
|
|
@ -78,7 +78,7 @@ SubstringFilter.prototype.matches = function(target) {
|
|||
return matcher.test(target[this.attribute]);
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ AddRequest.prototype.indexOf = function(attr) {
|
|||
throw new TypeError('attr (string) required');
|
||||
|
||||
for (var i = 0; i < this.attributes.length; i++)
|
||||
if (req.attributes[i].type === attr)
|
||||
if (this.attributes[i].type === attr)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
|
@ -133,3 +133,51 @@ AddRequest.prototype.getAttribute = function(name) {
|
|||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -16,8 +16,8 @@ var Protocol = require('../protocol');
|
|||
|
||||
var Ber = asn1.Ber;
|
||||
|
||||
var LDAP_BIND_SIMPLE = 'Simple';
|
||||
var LDAP_BIND_SASL = 'Sasl';
|
||||
var LDAP_BIND_SIMPLE = 'simple';
|
||||
var LDAP_BIND_SASL = 'sasl';
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ CompareRequest.prototype._parse = function(ber) {
|
|||
this.entry = dn.parse(ber.readString());
|
||||
|
||||
ber.readSequence();
|
||||
this.attribute = ber.readString();
|
||||
this.attribute = ber.readString().toLowerCase();
|
||||
this.value = ber.readString();
|
||||
|
||||
return true;
|
||||
|
|
|
@ -37,9 +37,7 @@ function DeleteRequest(options) {
|
|||
|
||||
var self = this;
|
||||
this.__defineGetter__('type', function() { return 'DeleteRequest'; });
|
||||
this.__defineGetter__('_dn', function() {
|
||||
return self.entry ? self.entry.toString() : '';
|
||||
});
|
||||
this.__defineGetter__('_dn', function() { return self.entry; });
|
||||
}
|
||||
util.inherits(DeleteRequest, LDAPMessage);
|
||||
module.exports = DeleteRequest;
|
||||
|
@ -48,11 +46,8 @@ module.exports = DeleteRequest;
|
|||
DeleteRequest.prototype._parse = function(ber, length) {
|
||||
assert.ok(ber);
|
||||
|
||||
// What a hack; LDAP is so annoying with its decisions of what to
|
||||
// shortcut, so this is totally a hack to work around the way the delete
|
||||
// message is structured
|
||||
this.entry = dn.parse(ber.buffer.slice(0, length).toString());
|
||||
ber._offset += length;
|
||||
this.entry = dn.parse(ber.buffer.slice(0, length).toString('utf8'));
|
||||
ber._offset += ber.length;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
|
|
@ -40,9 +40,7 @@ function ModifyRequest(options) {
|
|||
|
||||
var self = this;
|
||||
this.__defineGetter__('type', function() { return 'ModifyRequest'; });
|
||||
this.__defineGetter__('_dn', function() {
|
||||
return self.object ? self.object.toString() : '';
|
||||
});
|
||||
this.__defineGetter__('_dn', function() { return self.object; });
|
||||
}
|
||||
util.inherits(ModifyRequest, LDAPMessage);
|
||||
module.exports = ModifyRequest;
|
||||
|
|
|
@ -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) {
|
||||
if (typeof(obj) !== 'object')
|
||||
throw new TypeError('object required');
|
||||
|
|
|
@ -6,6 +6,7 @@ var util = require('util');
|
|||
var LDAPResult = require('./result');
|
||||
var SearchEntry = require('./search_entry');
|
||||
|
||||
var parseDN = require('../dn').parse;
|
||||
var Protocol = require('../protocol');
|
||||
|
||||
|
||||
|
@ -20,6 +21,9 @@ function SearchResponse(options) {
|
|||
|
||||
options.protocolOp = Protocol.LDAP_REP_SEARCH;
|
||||
LDAPResult.call(this, options);
|
||||
|
||||
this.attributes = options.attributes ? options.attributes.slice() : [];
|
||||
this.notAttributes = [];
|
||||
}
|
||||
util.inherits(SearchResponse, LDAPResult);
|
||||
module.exports = SearchResponse;
|
||||
|
@ -31,30 +35,67 @@ module.exports = SearchResponse;
|
|||
* @param {Object} entry an instance of SearchEntry.
|
||||
*/
|
||||
SearchResponse.prototype.send = function(entry) {
|
||||
if (!entry || !(entry instanceof SearchEntry))
|
||||
if (!entry || typeof(entry) !== 'object')
|
||||
throw new TypeError('entry (SearchEntry) required');
|
||||
|
||||
var self = this;
|
||||
|
||||
if (!(entry instanceof SearchEntry)) {
|
||||
if (!entry.dn)
|
||||
throw new Error('entry.dn required');
|
||||
if (!entry.attributes)
|
||||
throw new Error('entry.attributes required');
|
||||
|
||||
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];
|
||||
}
|
||||
});
|
||||
|
||||
entry = new SearchEntry({
|
||||
objectName: typeof(save.dn) === 'string' ? parseDN(save.dn) : save.dn,
|
||||
messageID: self.messageID,
|
||||
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');
|
||||
}
|
||||
|
||||
assert.ok(this.connection);
|
||||
|
||||
try {
|
||||
if (this.log.isDebugEnabled())
|
||||
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.json);
|
||||
|
||||
this.connection.write(entry.toBer());
|
||||
};
|
||||
|
||||
|
||||
SearchResponse.prototype.createSearchEntry = function(options) {
|
||||
if (options) {
|
||||
if (typeof(options) !== 'object')
|
||||
throw new TypeError('options must be an object');
|
||||
} else {
|
||||
options = {};
|
||||
} catch (e) {
|
||||
this.log.warn('%s failure to write message %j: %s',
|
||||
this.connection.ldap.id, this.json, e.toString());
|
||||
}
|
||||
|
||||
options.messageID = this.messageID;
|
||||
options.log4js = this.log4js;
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ var DeleteResponse = require('./messages/del_response');
|
|||
var ExtendedResponse = require('./messages/ext_response');
|
||||
var ModifyResponse = require('./messages/modify_response');
|
||||
var ModifyDNResponse = require('./messages/moddn_response');
|
||||
var SearchRequest = require('./messages/search_request');
|
||||
var SearchResponse = require('./messages/search_response');
|
||||
var UnbindResponse = require('./messages/unbind_response');
|
||||
|
||||
|
@ -108,7 +109,8 @@ function getResponse(req) {
|
|||
|
||||
var res = new Response({
|
||||
messageID: req.messageID,
|
||||
log4js: req.log4js
|
||||
log4js: req.log4js,
|
||||
attributes: ((req instanceof SearchRequest) ? req.attributes : undefined)
|
||||
});
|
||||
res.connection = req.connection;
|
||||
res.logId = req.logId;
|
||||
|
@ -203,11 +205,17 @@ function Server(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) {
|
||||
assert.ok(c);
|
||||
|
||||
if (c.type === 'unix') {
|
||||
c.remoteAddress = self.server.path;
|
||||
c.remotePort = c.fd;
|
||||
}
|
||||
|
||||
c.ldap = {
|
||||
id: c.remoteAddress + ':' + c.remotePort,
|
||||
config: options
|
||||
|
@ -242,11 +250,6 @@ function Server(options) {
|
|||
}
|
||||
|
||||
function newConnection(c) {
|
||||
if (c.type === 'unix') {
|
||||
c.remoteAddress = self.server.path;
|
||||
c.remotePort = c.fd;
|
||||
}
|
||||
|
||||
setupConnection(c);
|
||||
if (log.isTraceEnabled())
|
||||
log.trace('new connection from %s', c.ldap.id);
|
||||
|
@ -256,7 +259,7 @@ function Server(options) {
|
|||
});
|
||||
c.parser.on('message', function(req) {
|
||||
req.connection = c;
|
||||
req.logId = c.remoteAddress + '::' + req.messageID;
|
||||
req.logId = c.ldap.id + '::' + req.messageID;
|
||||
|
||||
if (log.isDebugEnabled())
|
||||
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]
|
||||
};
|
||||
} 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 {
|
||||
backend: routes['unbind'] ? routes['unbind'].backend : self,
|
||||
handlers: (routes['unbind'] && routes['unbind'][op] ?
|
||||
routes['unbind'][op] :
|
||||
[defaultUnbindHandler])
|
||||
handlers: getUnbindChain()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ test('new with args', function(t) {
|
|||
});
|
||||
t.ok(change);
|
||||
|
||||
t.equal(change.operation, 'Add');
|
||||
t.equal(change.operation, 'add');
|
||||
t.equal(change.modification.type, 'cn');
|
||||
t.equal(change.modification.vals.length, 2);
|
||||
t.equal(change.modification.vals[0], 'foo');
|
||||
|
@ -90,7 +90,7 @@ test('parse', function(t) {
|
|||
t.ok(change);
|
||||
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.vals.length, 2);
|
||||
t.equal(change.modification.vals[0], 'foo');
|
||||
|
|
|
@ -113,3 +113,32 @@ test('toBer', function(t) {
|
|||
|
||||
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();
|
||||
});
|
||||
|
|
|
@ -34,7 +34,7 @@ test('new with args', function(t) {
|
|||
entry: dn.parse('cn=test')
|
||||
});
|
||||
t.ok(req);
|
||||
t.equal(req.dn, 'cn=test');
|
||||
t.equal(req.dn.toString(), 'cn=test');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
@ -47,7 +47,7 @@ test('parse', function(t) {
|
|||
var reader = new BerReader(ber.buffer);
|
||||
reader.readSequence(0x4a);
|
||||
t.ok(req.parse(reader.buffer, reader.length));
|
||||
t.equal(req.dn, 'cn=test');
|
||||
t.equal(req.dn.toString(), 'cn=test');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ test('new with args', function(t) {
|
|||
t.ok(req);
|
||||
t.equal(req.dn.toString(), 'cn=foo, o=test');
|
||||
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.vals[0], 'person');
|
||||
t.end();
|
||||
|
@ -74,9 +74,9 @@ test('parse', function(t) {
|
|||
|
||||
var req = new ModifyRequest();
|
||||
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[0].operation, 'Replace');
|
||||
t.equal(req.changes[0].operation, 'replace');
|
||||
t.equal(req.changes[0].modification.type, 'objectclass');
|
||||
t.equal(req.changes[0].modification.vals[0], 'person');
|
||||
t.end();
|
||||
|
|
Loading…
Reference in New Issue