externalLdap: search and then bind
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user