diff --git a/lib/attribute.js b/lib/attribute.js index d6b25fa..328095b 100644 --- a/lib/attribute.js +++ b/lib/attribute.js @@ -88,6 +88,7 @@ Attribute.toBer = function(attr, ber) { Attribute.isAttribute = function(attr) { + if (!attr) return false; if (typeof(attr) !== 'object') return false; if (attr instanceof Attribute) return true; if (!attr.type || typeof(attr.type) !== 'string') return false; diff --git a/lib/change.js b/lib/change.js index a650237..bafb39f 100644 --- a/lib/change.js +++ b/lib/change.js @@ -15,8 +15,6 @@ function Change(options) { throw new TypeError('options must be an object'); if (options.operation && typeof(options.operation) !== 'string') throw new TypeError('options.operation must be a string'); - if (options.modification && !(options.modification instanceof Attribute)) - throw new TypeErrr('options.modification must be an Attribute'); } else { options = {}; } @@ -48,7 +46,24 @@ function Change(options) { throw new Error('Invalid operation type: 0x' + val.toString(16)); } }); - + this.__defineGetter__('modification', function() { + return self._modification; + }); + this.__defineSetter__('modification', function(attr) { + if (Attribute.isAttribute(attr)) + self._modification = attr; + Object.keys(attr).forEach(function(k) { + var _attr = new Attribute({type: k}); + if (Array.isArray(attr[k])) { + attr[k].forEach(function(v) { + _attr.addValue(v.toString()); + }); + } else { + _attr.addValue(attr[k].toString()); + } + self._modification = _attr; + }); + }); this.__defineGetter__('json', function() { return { operation: self.operation, @@ -57,7 +72,7 @@ function Change(options) { }); this.operation = options.operation || 'add'; - this.modification = options.modification || null; + this.modification = options.modification || {}; } module.exports = Change; @@ -67,8 +82,8 @@ Change.prototype.parse = function(ber) { ber.readSequence(); this._operation = ber.readEnumeration(); - this.modification = new Attribute(); - this.modification.parse(ber); + this._modification = new Attribute(); + this._modification.parse(ber); return true; }; @@ -79,7 +94,7 @@ Change.prototype.toBer = function(ber) { ber.startSequence(); ber.writeEnumeration(this._operation); - ber = this.modification.toBer(ber); + ber = this._modification.toBer(ber); ber.endSequence(); return ber; diff --git a/lib/client.js b/lib/client.js index a00fd40..23c3bda 100644 --- a/lib/client.js +++ b/lib/client.js @@ -282,21 +282,21 @@ Client.prototype.bind = function(name, credentials, controls, callback) { /** * Adds an entry to the LDAP server. * + * Entry can be either [Attribute] or a plain JS object where the + * values are either a plain value or an array of values. Any value (that's + * not an array) will get converted to a string, so keep that in mind. + * * @param {String} name the DN of the entry to add. - * @param {Array} attributes an array of Attributes to be added. + * @param {Object} entry an array of Attributes to be added or a JS object. * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err, res). * @throws {TypeError} on invalid input. */ -Client.prototype.add = function(name, attributes, controls, callback) { +Client.prototype.add = function(name, entry, controls, callback) { if (typeof(name) !== 'string') throw new TypeError('name (string) required'); - if (!Array.isArray(attributes)) - throw new TypeError('attributes ([Attribute]) required'); - attributes.forEach(function(a) { - if (!Attribute.isAttribute(a)) - throw new TypeError('attributes ([Attribute]) required'); - }); + if (typeof(entry) !== 'object') + throw new TypeError('entry (object) required'); if (typeof(controls) === 'function') { callback = controls; controls = []; @@ -306,9 +306,31 @@ Client.prototype.add = function(name, attributes, controls, callback) { if (typeof(callback) !== 'function') throw new TypeError('callback (function) required'); + if (Array.isArray(entry)) { + entry.forEach(function(a) { + if (!Attribute.isAttribute(a)) + throw new TypeError('entry must be an Array of Attributes'); + }); + } else { + var save = entry; + + entry = []; + Object.keys(save).forEach(function(k) { + var attr = new Attribute({type: k}); + if (Array.isArray(save[k])) { + save[k].forEach(function(v) { + attr.addValue(v.toString()); + }); + } else { + attr.addValue(save[k].toString()); + } + entry.push(attr); + }); + } + var req = new AddRequest({ entry: dn.parse(name), - attributes: attributes, + attributes: entry, controls: controls }); @@ -320,20 +342,16 @@ Client.prototype.add = function(name, attributes, controls, callback) { * Compares an attribute/value pair with an entry on the LDAP server. * * @param {String} name the DN of the entry to compare attributes with. - * @param {String} attribute name of an attribute to check. + * @param {String} attr name of an attribute to check. * @param {String} value value of an attribute to check. * @param {Control} controls (optional) either a Control or [Control]. * @param {Function} callback of the form f(err, boolean, res). * @throws {TypeError} on invalid input. */ -Client.prototype.compare = function(name, - attribute, - value, - controls, - callback) { +Client.prototype.compare = function(name, attr, value, controls, callback) { if (typeof(name) !== 'string') throw new TypeError('name (string) required'); - if (typeof(attribute) !== 'string') + if (typeof(attr) !== 'string') throw new TypeError('attribute (string) required'); if (typeof(value) !== 'string') throw new TypeError('value (string) required'); @@ -348,7 +366,7 @@ Client.prototype.compare = function(name, var req = new CompareRequest({ entry: dn.parse(name), - attribute: attribute, + attribute: attr, value: value, controls: controls }); diff --git a/lib/messages/search_entry.js b/lib/messages/search_entry.js index 8847e31..be40910 100644 --- a/lib/messages/search_entry.js +++ b/lib/messages/search_entry.js @@ -26,14 +26,6 @@ function SearchEntry(options) { 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'); - if (options.attributes && !Array.isArray(options.attributes)) - throw new TypeError('options.attributes must be an array[Attribute]'); - if (options.attributes && options.attributes.length) { - options.attributes.forEach(function(a) { - if (!(a instanceof Attribute)) - throw new TypeError('options.attributes must be an array[Attribute]'); - }); - } } else { options = {}; } @@ -42,12 +34,21 @@ function SearchEntry(options) { LDAPMessage.call(this, options); this.objectName = options.objectName || null; - this.attributes = options.attributes ? options.attributes.slice(0) : []; + this.setAttributes(options.attributes || []); var self = this; this.__defineGetter__('type', function() { return 'SearchEntry'; }); + this.__defineGetter__('object', function() { + var obj = { + dn: self.dn.toString() + }; + self.attributes.forEach(function(a) { + obj[a.type] = a.vals.length > 1 ? a.vals.slice() : a.vals[0]; + }); + return obj; + }); this.__defineGetter__('_dn', function() { - return self.objectName.toString(); + return self.objectName; }); } util.inherits(SearchEntry, LDAPMessage); @@ -62,6 +63,35 @@ SearchEntry.prototype.addAttribute = function(attr) { }; +SearchEntry.prototype.setAttributes = function(obj) { + if (typeof(obj) !== 'object') + throw new TypeError('object required'); + + if (Array.isArray(obj)) { + obj.forEach(function(a) { + if (!Attribute.isAttribute(a)) + throw new TypeError('entry must be an Array of Attributes'); + }); + this.attributes = obj; + } else { + var self = this; + + self.attributes = []; + Object.keys(obj).forEach(function(k) { + var attr = new Attribute({type: k}); + if (Array.isArray(obj[k])) { + obj[k].forEach(function(v) { + attr.addValue(v.toString()); + }); + } else { + attr.addValue(obj[k].toString()); + } + self.attributes.push(attr); + }); + } +}; + + SearchEntry.prototype._json = function(j) { assert.ok(j); diff --git a/tst/client.test.js b/tst/client.test.js index 3d8f04d..2116f26 100644 --- a/tst/client.test.js +++ b/tst/client.test.js @@ -81,12 +81,10 @@ test('setup', function(t) { server.search(SUFFIX, function(req, res, next) { var e = res.createSearchEntry({ objectName: req.dn, - attributes: [ - new Attribute({ - type: 'cn', - vals: ['test'] - }) - ] + attributes: { + cn: ['unit', 'test'], + sn: 'testy' + } }); res.send(e); res.send(e); @@ -154,6 +152,20 @@ test('add success', function(t) { }); +test('add success with object', function(t) { + var entry = { + cn: ['unit', 'add'], + sn: 'test' + }; + client.add('cn=add, ' + SUFFIX, entry, function(err, res) { + t.ifError(err); + t.ok(res); + t.equal(res.status, 0); + t.end(); + }); +}); + + test('compare success', function(t) { client.compare('cn=compare, ' + SUFFIX, 'cn', 'test', function(err, matched, @@ -238,6 +250,22 @@ test('modify success', function(t) { }); +test('modify change plain object success', function(t) { + var change = new Change({ + type: 'Replace', + modification: { + cn: 'test' + } + }); + client.modify('cn=modify, ' + SUFFIX, change, function(err, res) { + t.ifError(err); + t.ok(res); + t.equal(res.status, 0); + t.end(); + }); +}); + + test('modify array success', function(t) { var changes = [ new Change({ @@ -294,6 +322,7 @@ test('search basic', function(t) { t.equal(entry.dn.toString(), 'cn=test, ' + SUFFIX); t.ok(entry.attributes); t.ok(entry.attributes.length); + t.ok(entry.object); gotEntry++; }); res.on('error', function(err) {