diff --git a/src/ldap.js b/src/ldap.js index 7e600254f..eb38447d3 100644 --- a/src/ldap.js +++ b/src/ldap.js @@ -7,6 +7,7 @@ exports = module.exports = { var assert = require('assert'), apps = require('./apps.js'), + async = require('async'), config = require('./config.js'), DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:ldap'), @@ -40,56 +41,64 @@ function getAppByRequest(req, callback) { function userSearch(req, res, next) { debug('user search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id); - user.list(function (error, result) { - if (error) return next(new ldap.OperationsError(error.toString())); + getAppByRequest(req, function (error, app) { + if (error) return next(error); - // send user objects - result.forEach(function (entry) { - // skip entries with empty username. Some apps like owncloud can't deal with this - if (!entry.username) return; + user.list(function (error, result) { + if (error) return next(new ldap.OperationsError(error.toString())); - var dn = ldap.parseDN('cn=' + entry.id + ',ou=users,dc=cloudron'); + async.filter(result, apps.hasAccessTo.bind(null, app), function (error, result) { + if (error) return next(new ldap.OperationsError(error.toString())); - var groups = [ GROUP_USERS_DN ]; - if (entry.admin) groups.push(GROUP_ADMINS_DN); + // send user objects + result.forEach(function (entry) { + // skip entries with empty username. Some apps like owncloud can't deal with this + if (!entry.username) return; - var displayName = entry.displayName || entry.username || ''; // displayName can be empty and username can be null - var nameParts = displayName.split(' '); - var firstName = nameParts[0]; - var lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : ''; // choose last part, if it exists + var dn = ldap.parseDN('cn=' + entry.id + ',ou=users,dc=cloudron'); - var obj = { - dn: dn.toString(), - attributes: { - objectclass: ['user'], - objectcategory: 'person', - cn: entry.id, - uid: entry.id, - mail: entry.email, - mailAlternateAddress: entry.alternateEmail, - displayname: displayName, - givenName: firstName, - username: entry.username, - samaccountname: entry.username, // to support ActiveDirectory clients - isadmin: entry.admin ? 1 : 0, - memberof: groups - } - }; + var groups = [ GROUP_USERS_DN ]; + if (entry.admin) groups.push(GROUP_ADMINS_DN); - // http://www.zytrax.com/books/ldap/ape/core-schema.html#sn has 'name' as SUP which is a DirectoryString - // which is required to have atleast one character if present - if (lastName.length !== 0) obj.attributes.sn = lastName; + var displayName = entry.displayName || entry.username || ''; // displayName can be empty and username can be null + var nameParts = displayName.split(' '); + var firstName = nameParts[0]; + var lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : ''; // choose last part, if it exists - // ensure all filter values are also lowercase - var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null); - if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString())); + var obj = { + dn: dn.toString(), + attributes: { + objectclass: ['user'], + objectcategory: 'person', + cn: entry.id, + uid: entry.id, + mail: entry.email, + mailAlternateAddress: entry.alternateEmail, + displayname: displayName, + givenName: firstName, + username: entry.username, + samaccountname: entry.username, // to support ActiveDirectory clients + isadmin: entry.admin ? 1 : 0, + memberof: groups + } + }; - if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) { - res.send(obj); - } + // http://www.zytrax.com/books/ldap/ape/core-schema.html#sn has 'name' as SUP which is a DirectoryString + // which is required to have atleast one character if present + if (lastName.length !== 0) obj.attributes.sn = lastName; + + // ensure all filter values are also lowercase + var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null); + if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString())); + + if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) { + res.send(obj); + } + }); + + res.end(); + }); }); - - res.end(); }); } diff --git a/src/test/ldap-test.js b/src/test/ldap-test.js index 562a47b0a..b1a3228d1 100644 --- a/src/test/ldap-test.js +++ b/src/test/ldap-test.js @@ -38,6 +38,12 @@ var USER_1 = { email: 'USER1@email.com', displayName: 'User 1' }; +var USER_2 = { + username: 'Username2', + password: 'Username2pass?12345', + email: 'USER2@email.com', + displayName: 'User 2' +}; var GROUP_ID, GROUP_NAME = 'developers'; @@ -98,6 +104,15 @@ function setup(done) { callback(null); }); }, + function (callback) { + user.create(USER_2.username, USER_2.password, USER_2.email, USER_0.displayName, AUDIT_SOURCE, { invitor: USER_0 }, function (error, result) { + if (error) return callback(error); + + USER_2.id = result.id; + + callback(null); + }); + }, function (callback) { groups.create(GROUP_NAME, function (error, result) { if (error) return callback(error); @@ -409,6 +424,66 @@ describe('Ldap', function () { }); }); }); + + it ('does not list users who have no access', function (done) { + appdb.update(APP_0.id, { accessRestriction: { users: [], groups: [] } }, function (error) { + expect(error).to.be(null); + + var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') }); + + var opts = { + filter: 'objectcategory=person' + }; + + client.search('ou=users,dc=cloudron', opts, function (error, result) { + expect(error).to.be(null); + expect(result).to.be.an(EventEmitter); + + var entries = []; + + result.on('searchEntry', function (entry) { entries.push(entry.object); }); + result.on('error', done); + result.on('end', function (result) { + expect(result.status).to.equal(0); + expect(entries.length).to.equal(0); + + appdb.update(APP_0.id, { accessRestriction: null }, done); + }); + }); + }); + }); + + it ('does not list users who have access', function (done) { + appdb.update(APP_0.id, { accessRestriction: { users: [], groups: [ GROUP_ID ] } }, function (error) { + expect(error).to.be(null); + + var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') }); + + var opts = { + filter: 'objectcategory=person' + }; + + client.search('ou=users,dc=cloudron', opts, function (error, result) { + expect(error).to.be(null); + expect(result).to.be.an(EventEmitter); + + var entries = []; + + result.on('searchEntry', function (entry) { entries.push(entry.object); }); + result.on('error', done); + result.on('end', function (result) { + expect(result.status).to.equal(0); + expect(entries.length).to.equal(2); + entries.sort(function (a, b) { return a.username > b.username; }); + + expect(entries[0].username).to.equal(USER_0.username.toLowerCase()); + expect(entries[1].username).to.equal(USER_1.username.toLowerCase()); + + appdb.update(APP_0.id, { accessRestriction: null }, done); + }); + }); + }); + }); }); describe('search groups', function () { @@ -435,9 +510,10 @@ describe('Ldap', function () { entries.sort(function (a, b) { return a.username < b.username; }); expect(entries[0].cn).to.equal('users'); - expect(entries[0].memberuid.length).to.equal(2); + expect(entries[0].memberuid.length).to.equal(3); expect(entries[0].memberuid[0]).to.equal(USER_0.id); expect(entries[0].memberuid[1]).to.equal(USER_1.id); + expect(entries[0].memberuid[2]).to.equal(USER_2.id); expect(entries[1].cn).to.equal('admins'); // if only one entry, the array becomes a string :-/ expect(entries[1].memberuid).to.equal(USER_0.id); @@ -465,9 +541,10 @@ describe('Ldap', function () { expect(result.status).to.equal(0); expect(entries.length).to.equal(2); expect(entries[0].cn).to.equal('users'); - expect(entries[0].memberuid.length).to.equal(2); + expect(entries[0].memberuid.length).to.equal(3); expect(entries[0].memberuid[0]).to.equal(USER_0.id); expect(entries[0].memberuid[1]).to.equal(USER_1.id); + expect(entries[0].memberuid[2]).to.equal(USER_2.id); expect(entries[1].cn).to.equal('admins'); // if only one entry, the array becomes a string :-/ expect(entries[1].memberuid).to.equal(USER_0.id); @@ -495,7 +572,7 @@ describe('Ldap', function () { expect(result.status).to.equal(0); expect(entries.length).to.equal(1); expect(entries[0].cn).to.equal('users'); - expect(entries[0].memberuid.length).to.equal(2); + expect(entries[0].memberuid.length).to.equal(3); done(); }); });