// Copyright 2011 Mark Cavage, Inc. All rights reserved. var Logger = require('bunyan'); var test = require('tap').test; var uuid = require('node-uuid'); ///--- Globals var BIND_DN = 'cn=root'; var BIND_PW = 'secret'; var SOCKET = '/tmp/.' + uuid(); var SUFFIX = 'dc=test'; var ldap; var Attribute; var Change; var client; var server; ///--- Tests test('setup', function (t) { ldap = require('../lib/index'); t.ok(ldap); t.ok(ldap.createClient); t.ok(ldap.createServer); t.ok(ldap.Attribute); t.ok(ldap.Change); Attribute = ldap.Attribute; Change = ldap.Change; server = ldap.createServer(); t.ok(server); server.bind(BIND_DN, function (req, res, next) { if (req.credentials !== BIND_PW) return next(new ldap.InvalidCredentialsError('Invalid password')); res.end(); return next(); }); server.add(SUFFIX, function (req, res, next) { res.end(); return next(); }); server.compare(SUFFIX, function (req, res, next) { res.end(req.value === 'test'); return next(); }); server.del(SUFFIX, function (req, res, next) { res.end(); return next(); }); // LDAP whoami server.exop('1.3.6.1.4.1.4203.1.11.3', function (req, res, next) { res.value = 'u:xxyyz@EXAMPLE.NET'; res.end(); return next(); }); server.modify(SUFFIX, function (req, res, next) { res.end(); return next(); }); server.modifyDN(SUFFIX, function (req, res, next) { res.end(); return next(); }); server.search('dc=timeout', function (req, res, next) { // Haha client! }); server.search(SUFFIX, function (req, res, next) { if (req.dn.equals('cn=ref,' + SUFFIX)) { res.send(res.createSearchReference('ldap://localhost')); } else if (req.dn.equals('cn=bin,' + SUFFIX)) { res.send(res.createSearchEntry({ objectName: req.dn, attributes: { 'foo;binary': 'wr0gKyDCvCA9IMK+', 'gb18030': new Buffer([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA]), 'objectclass': 'binary' } })); } else { var e = res.createSearchEntry({ objectName: req.dn, attributes: { cn: ['unit', 'test'], SN: 'testy' } }); res.send(e); res.send(e); } res.end(); return next(); }); server.search('dc=empty', function (req, res, next) { res.send({ dn: 'dc=empty', attributes: { member: [], 'member;range=0-1': ['cn=user1, dc=empty', 'cn=user2, dc=empty'] } }); res.end(); return next(); }); server.unbind(function (req, res, next) { res.end(); return next(); }); server.listen(SOCKET, function () { client = ldap.createClient({ connectTimeout: 100, socketPath: SOCKET, maxConnections: process.env.LDAP_MAX_CONNS || 5, idleTimeoutMillis: 10, log: new Logger({ name: 'ldapjs_unit_test', stream: process.stderr, level: (process.env.LOG_LEVEL || 'info'), serializers: Logger.stdSerializers }) }); t.ok(client); t.end(); }); }); test('simple bind failure', function (t) { try { client.bind(BIND_DN, uuid(), function (err, res) { t.ok(err); t.notOk(res); t.ok(err instanceof ldap.InvalidCredentialsError); t.ok(err instanceof Error); t.ok(err.dn); t.ok(err.message); t.ok(err.stack); t.end(); }); } catch (e) { console.log(e.stack); process.exit(1); } }); test('simple bind success', function (t) { client.bind(BIND_DN, BIND_PW, function (err, res) { t.ifError(err); t.ok(res); t.equal(res.status, 0); t.end(); }); }); test('add success', function (t) { var attrs = [ new Attribute({ type: 'cn', vals: ['test'] }) ]; client.add('cn=add, ' + SUFFIX, attrs, function (err, res) { t.ifError(err); t.ok(res); t.equal(res.status, 0); t.end(); }); }); 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, res) { t.ifError(err); t.ok(matched); t.ok(res); t.end(); }); }); test('compare false', function (t) { client.compare('cn=compare, ' + SUFFIX, 'cn', 'foo', function (err, matched, res) { t.ifError(err); t.notOk(matched); t.ok(res); t.end(); }); }); test('compare bad suffix', function (t) { client.compare('cn=' + uuid(), 'cn', 'foo', function (err, matched, res) { t.ok(err); t.ok(err instanceof ldap.NoSuchObjectError); t.notOk(matched); t.notOk(res); t.end(); }); }); test('delete success', function (t) { client.del('cn=delete, ' + SUFFIX, function (err, res) { t.ifError(err); t.ok(res); t.end(); }); }); test('exop success', function (t) { client.exop('1.3.6.1.4.1.4203.1.11.3', function (err, value, res) { t.ifError(err); t.ok(value); t.ok(res); t.equal(value, 'u:xxyyz@EXAMPLE.NET'); t.end(); }); }); test('exop invalid', function (t) { client.exop('1.2.3.4', function (err, res) { t.ok(err); t.ok(err instanceof ldap.ProtocolError); t.notOk(res); t.end(); }); }); test('bogus exop (GH-17)', function (t) { client.exop('cn=root', function (err, value) { t.ok(err); t.end(); }); }); test('modify success', function (t) { var change = new Change({ type: 'Replace', modification: new Attribute({ type: 'cn', vals: ['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 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({ operation: 'Replace', modification: new Attribute({ type: 'cn', vals: ['test'] }) }), new Change({ operation: 'Delete', modification: new Attribute({ type: 'sn' }) }) ]; client.modify('cn=modify, ' + SUFFIX, changes, function (err, res) { t.ifError(err); t.ok(res); t.equal(res.status, 0); t.end(); }); }); test('modify change plain object success (GH-31)', function (t) { var change = { type: 'replace', modification: { cn: 'test', sn: 'bar' } }; client.modify('cn=modify, ' + SUFFIX, change, function (err, res) { t.ifError(err); t.ok(res); t.equal(res.status, 0); t.end(); }); }); test('modify DN new RDN only', function (t) { client.modifyDN('cn=old, ' + SUFFIX, 'cn=new', function (err, res) { t.ifError(err); t.ok(res); t.equal(res.status, 0); t.end(); }); }); test('modify DN new superior', function (t) { client.modifyDN('cn=old, ' + SUFFIX, 'cn=new, dc=foo', function (err, res) { t.ifError(err); t.ok(res); t.equal(res.status, 0); t.end(); }); }); test('search basic', function (t) { client.search('cn=test, ' + SUFFIX, '(objectclass=*)', function (err, res) { t.ifError(err); t.ok(res); var gotEntry = 0; res.on('searchEntry', function (entry) { t.ok(entry); t.ok(entry instanceof ldap.SearchEntry); t.equal(entry.dn.toString(), 'cn=test, ' + SUFFIX); t.ok(entry.attributes); t.ok(entry.attributes.length); t.equal(entry.attributes[0].type, 'cn'); t.equal(entry.attributes[1].type, 'SN'); t.ok(entry.object); gotEntry++; }); 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, 2); t.end(); }); }); }); 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('search empty attribute', function (t) { client.search('dc=empty', '(objectclass=*)', function (err, res) { t.ifError(err); t.ok(res); var gotEntry = 0; res.on('searchEntry', function (entry) { var obj = entry.toObject(); t.equal('dc=empty', obj.dn); t.ok(obj.member); t.equal(obj.member.length, 0); t.ok(obj['member;range=0-1']); t.ok(obj['member;range=0-1'].length); gotEntry++; }); 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, 1); t.end(); }); }); }); test('GH-21 binary attributes', function (t) { client.search('cn=bin, ' + SUFFIX, '(objectclass=*)', function (err, res) { t.ifError(err); t.ok(res); var gotEntry = 0; var expect = new Buffer('\u00bd + \u00bc = \u00be', 'utf8'); var expect2 = new Buffer([0xB5, 0xE7, 0xCA, 0xD3, 0xBB, 0xFA]); res.on('searchEntry', function (entry) { t.ok(entry); t.ok(entry instanceof ldap.SearchEntry); t.equal(entry.dn.toString(), 'cn=bin, ' + SUFFIX); t.ok(entry.attributes); t.ok(entry.attributes.length); t.equal(entry.attributes[0].type, 'foo;binary'); t.equal(entry.attributes[0].vals[0], expect.toString('base64')); t.equal(entry.attributes[0].buffers[0].toString('base64'), expect.toString('base64')); t.ok(entry.attributes[1].type, 'gb18030'); t.equal(entry.attributes[1].buffers.length, 1); t.equal(expect2.length, entry.attributes[1].buffers[0].length); for (var i = 0; i < expect2.length; i++) t.equal(expect2[i], entry.attributes[1].buffers[0][i]); t.ok(entry.object); gotEntry++; }); 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, 1); t.end(); }); }); }); test('GH-23 case insensitive attribute filtering', function (t) { var opts = { filter: '(objectclass=*)', attributes: ['Cn'] }; client.search('cn=test, ' + SUFFIX, opts, function (err, res) { t.ifError(err); t.ok(res); var gotEntry = 0; res.on('searchEntry', function (entry) { t.ok(entry); t.ok(entry instanceof ldap.SearchEntry); t.equal(entry.dn.toString(), 'cn=test, ' + SUFFIX); t.ok(entry.attributes); t.ok(entry.attributes.length); t.equal(entry.attributes[0].type, 'cn'); t.ok(entry.object); gotEntry++; }); 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, 2); t.end(); }); }); }); test('GH-24 attribute selection of *', function (t) { var opts = { filter: '(objectclass=*)', attributes: ['*'] }; client.search('cn=test, ' + SUFFIX, opts, function (err, res) { t.ifError(err); t.ok(res); var gotEntry = 0; res.on('searchEntry', function (entry) { t.ok(entry); t.ok(entry instanceof ldap.SearchEntry); t.equal(entry.dn.toString(), 'cn=test, ' + SUFFIX); t.ok(entry.attributes); t.ok(entry.attributes.length); t.equal(entry.attributes[0].type, 'cn'); t.equal(entry.attributes[1].type, 'SN'); t.ok(entry.object); gotEntry++; }); 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, 2); t.end(); }); }); }); test('abandon (GH-27)', function (t) { client.abandon(401876543, function (err) { t.ifError(err); t.end(); }); }); test('search timeout (GH-51)', function (t) { client.timeout = 250; client.search('dc=timeout', 'objectclass=*', function (err, res) { t.ifError(err); res.on('error', function () { t.end(); }); }); }); test('unbind (GH-30)', function (t) { client.unbind(function (err) { t.ifError(err); t.end(); }); }); test('shutdown', function (t) { server.on('close', function () { t.end(); }); server.close(); });