diff --git a/src/externalldap.js b/src/externalldap.js index 3780745f0..5ba2f2377 100644 --- a/src/externalldap.js +++ b/src/externalldap.js @@ -35,6 +35,7 @@ function removePrivateFields(ldapConfig) { // performs service bind if required function getClient(externalLdapConfig, callback) { + assert.strictEqual(typeof externalLdapConfig, 'object'); assert.strictEqual(typeof callback, 'function'); // basic validation to not crash @@ -59,6 +60,47 @@ function getClient(externalLdapConfig, callback) { }); } +function ldapSearch(externalLdapConfig, options, callback) { + assert.strictEqual(typeof externalLdapConfig, 'object'); + assert.strictEqual(typeof options, 'object'); + assert.strictEqual(typeof callback, 'function'); + + getClient(externalLdapConfig, function (error, client) { + if (error) return callback(error); + + let searchOptions = { + paged: true, + filter: ldap.parseFilter(externalLdapConfig.filter), + scope: 'sub' // We may have to make this configurable + }; + + if (options.filter) { // https://github.com/ldapjs/node-ldapjs/blob/master/docs/filters.md + let extraFilter = ldap.parseFilter(options.filter); + searchOptions.filter = new ldap.AndFilter({ filters: [ extraFilter, searchOptions.filter ] }); + } + + debug(`Listing users at ${externalLdapConfig.baseDn} with filter ${searchOptions.filter.toString()}`); + + client.search(externalLdapConfig.baseDn, searchOptions, function (error, result) { + if (error instanceof ldap.NoSuchObjectError) return callback(new BoxError(BoxError.NOT_FOUND)); + if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error)); + + let ldapUsers = []; + + result.on('searchEntry', entry => ldapUsers.push(entry.object)); + result.on('error', error => callback(new BoxError(BoxError.EXTERNAL_ERROR, error))); + + result.on('end', function (result) { + client.unbind(); + + if (result.status !== 0) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Server returned status ' + result.status)); + + callback(null, ldapUsers); + }); + }); + }); +} + function testConfig(config, callback) { assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof callback, 'function'); @@ -98,12 +140,15 @@ function verifyPassword(user, password, callback) { if (error) return callback(error); if (externalLdapConfig.provider === 'noop') return callback(new BoxError(BoxError.BAD_STATE, 'not enabled')); - getClient(externalLdapConfig, function (error, client) { + ldapSearch(externalLdapConfig, { filter: `${externalLdapConfig.usernameField}=${user.username}` }, function (error, ldapUsers) { if (error) return callback(error); + if (ldapUsers.length === 0) return callback(new BoxError(BoxError.NOT_FOUND)); + if (ldapUsers.length > 1) return callback(new BoxError(BoxError.CONFLICT)); - const dn = `uid=${user.username},${externalLdapConfig.baseDn}`; + const userDn = ldapUsers[0].dn; + let client = ldap.createClient({ url: externalLdapConfig.url }); - client.bind(dn, password, function (error) { + client.bind(userDn, password, function (error) { if (error instanceof ldap.InvalidCredentialsError) return callback(new BoxError(BoxError.INVALID_CREDENTIALS)); if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error)); @@ -142,79 +187,57 @@ function sync(progressCallback, callback) { if (error) return callback(error); if (externalLdapConfig.provider === 'noop') return callback(new BoxError(BoxError.BAD_STATE, 'not enabled')); - getClient(externalLdapConfig, function (error, client) { + ldapSearch(externalLdapConfig, {}, function (error, ldapUsers) { if (error) return callback(error); - var opts = { - paged: true, - filter: externalLdapConfig.filter, - scope: 'sub' // We may have to make this configurable - }; + debug(`Found ${ldapUsers.length} users`); - debug(`Listing users at ${externalLdapConfig.baseDn} with filter ${externalLdapConfig.filter} and usernameField=${externalLdapConfig.usernameField}`); + // we ignore all errors here and just log them for now + async.eachSeries(ldapUsers, function (user, iteratorCallback) { + const username = user[externalLdapConfig.usernameField]; + const email = user.mail; + const displayName = user.cn; // user.giveName + ' ' + user.sn - client.search(externalLdapConfig.baseDn, opts, function (error, result) { - if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error)); + if (!username || !email || !displayName) { + debug(`[empty username/email/displayName] username=${username} email=${email} displayName=${displayName} usernameField=${externalLdapConfig.usernameField}`); + return iteratorCallback(); + } - var ldapUsers = []; + users.getByUsername(username, function (error, result) { + if (error && error.reason !== BoxError.NOT_FOUND) { + debug(`Could not find user with username ${username}: ${error.message}`); + return iteratorCallback(); + } - result.on('searchEntry', entry => ldapUsers.push(entry.object)); - result.on('error', error => callback(new BoxError(BoxError.EXTERNAL_ERROR, error))); + if (error) { + debug(`[adding user] username=${username} email=${email} displayName=${displayName}`); - result.on('end', function (result) { - if (result.status !== 0) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Server returned status ' + result.status)); - - debug(`Found ${ldapUsers.length} users`); - - // we ignore all errors here and just log them for now - async.eachSeries(ldapUsers, function (user, iteratorCallback) { - const username = user[externalLdapConfig.usernameField]; - const email = user.mail; - const displayName = user.cn; // user.giveName + ' ' + user.sn - - if (!username || !email || !displayName) { - debug(`[empty username/email/displayName] username=${username} email=${email} displayName=${displayName} usernameField=${externalLdapConfig.usernameField}`); - return iteratorCallback(); - } - - users.getByUsername(username, function (error, result) { - if (error && error.reason !== BoxError.NOT_FOUND) { - debug(`Could not find user with username ${username}: ${error.message}`); - return iteratorCallback(); - } - - if (error) { - debug(`[adding user] username=${username} email=${email} displayName=${displayName}`); - - users.create(username, null /* password */, email, displayName, { source: 'ldap' }, auditSource.EXTERNAL_LDAP_TASK, function (error) { - if (error) console.error('Failed to create user', user, error); - iteratorCallback(); - }); - } else if (result.source !== 'ldap') { - debug(`[conflicting user] username=${username} email=${email} displayName=${displayName}`); - - iteratorCallback(); - } else if (result.email !== email || result.displayName !== displayName) { - debug(`[updating user] username=${username} email=${email} displayName=${displayName}`); - - users.update(result.id, { email: email, fallbackEmail: email, displayName: displayName }, auditSource.EXTERNAL_LDAP_TASK, function (error) { - if (error) debug('Failed to update user', user, error); - - iteratorCallback(); - }); - } else { - // user known and up-to-date - debug(`[up-to-date user] username=${username} email=${email} displayName=${displayName}`); - - iteratorCallback(); - } + users.create(username, null /* password */, email, displayName, { source: 'ldap' }, auditSource.EXTERNAL_LDAP_TASK, function (error) { + if (error) console.error('Failed to create user', user, error); + iteratorCallback(); }); - }, function () { - debug('User sync done.'); - client.unbind(); - callback(); - }); + } else if (result.source !== 'ldap') { + debug(`[conflicting user] username=${username} email=${email} displayName=${displayName}`); + + iteratorCallback(); + } else if (result.email !== email || result.displayName !== displayName) { + debug(`[updating user] username=${username} email=${email} displayName=${displayName}`); + + users.update(result.id, { email: email, fallbackEmail: email, displayName: displayName }, auditSource.EXTERNAL_LDAP_TASK, function (error) { + if (error) debug('Failed to update user', user, error); + + iteratorCallback(); + }); + } else { + // user known and up-to-date + debug(`[up-to-date user] username=${username} email=${email} displayName=${displayName}`); + + iteratorCallback(); + } }); + }, function (error) { + debug('sync: ldap sync is done', error); + callback(error); }); }); });