Files
cloudron-box/src/externalldap.js
T

203 lines
8.4 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
verifyPassword: verifyPassword,
testConfig: testConfig,
2019-08-29 17:19:51 +02:00
startSyncer: startSyncer,
sync: sync
};
var assert = require('assert'),
async = require('async'),
2019-08-29 22:15:48 +02:00
auditsource = require('./auditsource.js'),
2019-10-24 13:41:41 -07:00
BoxError = require('./boxerror.js'),
2019-08-30 19:11:27 +02:00
debug = require('debug')('box:externalldap'),
ldap = require('ldapjs'),
settings = require('./settings.js'),
2019-08-29 17:19:51 +02:00
tasks = require('./tasks.js'),
2019-10-24 15:12:58 -07:00
users = require('./users.js');
// performs service bind if required
2019-08-29 17:19:51 +02:00
function getClient(externalLdapConfig, callback) {
assert.strictEqual(typeof callback, 'function');
2019-08-29 22:51:27 +02:00
// basic validation to not crash
2019-10-24 13:41:41 -07:00
try { ldap.parseDN(externalLdapConfig.baseDn); } catch (e) { return callback(new BoxError(BoxError.BAD_FIELD, 'invalid baseDn')); }
try { ldap.parseFilter(externalLdapConfig.filter); } catch (e) { return callback(new BoxError(BoxError.BAD_FIELD, 'invalid filter')); }
if (externalLdapConfig.bindDn) try { ldap.parseFilter(externalLdapConfig.bindDn); } catch (e) { return callback(new BoxError(BoxError.INVALID_CREDENTIALS)); }
2019-08-29 22:51:27 +02:00
2019-08-29 17:19:51 +02:00
var client;
try {
client = ldap.createClient({ url: externalLdapConfig.url });
} catch (e) {
2019-10-24 13:41:41 -07:00
if (e instanceof ldap.ProtocolError) return callback(new BoxError(BoxError.BAD_FIELD, 'url protocol is invalid'));
return callback(new BoxError(BoxError.INTERNAL_ERROR, e));
2019-08-29 17:19:51 +02:00
}
2019-08-29 17:19:51 +02:00
if (!externalLdapConfig.bindDn) return callback(null, client);
2019-08-29 17:19:51 +02:00
client.bind(externalLdapConfig.bindDn, externalLdapConfig.bindPassword, function (error) {
2019-10-24 13:41:41 -07:00
if (error instanceof ldap.InvalidCredentialsError) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error));
2019-08-29 17:19:51 +02:00
callback(null, client, externalLdapConfig);
});
}
function testConfig(config, callback) {
assert.strictEqual(typeof config, 'object');
assert.strictEqual(typeof callback, 'function');
2019-08-29 12:28:41 +02:00
if (!config.enabled) return callback();
2019-10-24 13:41:41 -07:00
if (!config.url) return callback(new BoxError(BoxError.BAD_FIELD, 'url must not be empty'));
if (!config.baseDn) return callback(new BoxError(BoxError.BAD_FIELD, 'basedn must not be empty'));
if (!config.filter) return callback(new BoxError(BoxError.BAD_FIELD, 'filter must not be empty'));
2019-08-29 17:19:51 +02:00
getClient(config, function (error, client) {
if (error) return callback(error);
var opts = {
filter: config.filter,
scope: 'sub'
};
client.search(config.baseDn, opts, function (error, result) {
2019-10-24 13:41:41 -07:00
if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error));
2019-10-23 15:57:01 -07:00
result.on('searchEntry', function (/* entry */) {});
2019-10-24 13:41:41 -07:00
result.on('error', function (error) { callback(new BoxError(BoxError.BAD_FIELD, `Unable to search directory: ${error.message}`)); });
2019-10-23 15:57:01 -07:00
result.on('end', function (/* result */) { callback(); });
});
});
}
function verifyPassword(user, password, callback) {
assert.strictEqual(typeof user, 'object');
assert.strictEqual(typeof password, 'string');
assert.strictEqual(typeof callback, 'function');
settings.getExternalLdapConfig(function (error, externalLdapConfig) {
2019-10-24 13:41:41 -07:00
if (error) return callback(new BoxError(BoxError.INTERNAL_ERROR, error));
if (!externalLdapConfig.enabled) return callback(new BoxError(BoxError.BAD_STATE, 'not enabled'));
getClient(externalLdapConfig, function (error, client) {
if (error) return callback(error);
const dn = `uid=${user.username},${externalLdapConfig.baseDn}`;
client.bind(dn, password, function (error) {
2019-10-24 13:41:41 -07:00
if (error instanceof ldap.InvalidCredentialsError) return callback(new BoxError(BoxError.INVALID_CREDENTIALS));
if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error));
callback();
});
});
});
}
2019-08-29 17:19:51 +02:00
function startSyncer(callback) {
assert.strictEqual(typeof callback, 'function');
2019-08-29 17:19:51 +02:00
settings.getExternalLdapConfig(function (error, externalLdapConfig) {
2019-10-24 13:41:41 -07:00
if (error) return callback(new BoxError(BoxError.INTERNAL_ERROR, error));
if (!externalLdapConfig.enabled) return callback(new BoxError(BoxError.BAD_STATE, 'not enabled'));
2019-08-29 17:19:51 +02:00
tasks.add(tasks.TASK_SYNC_EXTERNAL_LDAP, [], function (error, taskId) {
2019-10-24 13:41:41 -07:00
if (error) return callback(new BoxError(BoxError.INTERNAL_ERROR, error));
2019-08-29 17:19:51 +02:00
tasks.startTask(taskId, {}, function (error, result) {
debug('sync: done', error, result);
});
2019-08-29 17:19:51 +02:00
callback(null, taskId);
});
});
}
function sync(progressCallback, callback) {
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
2019-08-30 19:11:27 +02:00
debug('Start user syncing ...');
2019-08-29 17:19:51 +02:00
settings.getExternalLdapConfig(function (error, externalLdapConfig) {
2019-10-24 13:41:41 -07:00
if (error) return callback(new BoxError(BoxError.INTERNAL_ERROR, error));
if (!externalLdapConfig.enabled) return callback(new BoxError(BoxError.BAD_STATE, 'not enabled'));
2019-08-29 17:19:51 +02:00
getClient(externalLdapConfig, function (error, client) {
if (error) return callback(error);
var opts = {
2019-08-30 19:11:27 +02:00
paged: true,
2019-08-29 17:19:51 +02:00
filter: externalLdapConfig.filter,
2019-08-29 22:51:27 +02:00
scope: 'sub' // We may have to make this configurable
2019-08-29 17:19:51 +02:00
};
2019-08-30 19:11:27 +02:00
debug(`Listing users at ${externalLdapConfig.baseDn} with filter ${externalLdapConfig.filter}`);
2019-08-29 17:19:51 +02:00
client.search(externalLdapConfig.baseDn, opts, function (error, result) {
2019-10-24 13:41:41 -07:00
if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error));
2019-08-29 17:19:51 +02:00
var ldapUsers = [];
2019-08-29 22:15:48 +02:00
result.on('searchEntry', function (entry) {
ldapUsers.push(entry.object);
2019-08-29 17:19:51 +02:00
});
result.on('error', function (error) {
2019-10-24 13:41:41 -07:00
callback(new BoxError(BoxError.EXTERNAL_ERROR, error));
2019-08-29 17:19:51 +02:00
});
2019-08-29 22:15:48 +02:00
result.on('end', function (result) {
2019-10-24 13:41:41 -07:00
if (result.status !== 0) return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Server returned status ' + result.status));
2019-08-30 19:11:27 +02:00
debug(`Found ${ldapUsers.length} users`);
// we ignore all errors here and just log them for now
async.eachSeries(ldapUsers, function (user, callback) {
// ignore the bindDn user if any
if (user.dn === externalLdapConfig.bindDn) return callback();
users.getByUsername(user.uid, function (error, result) {
2019-10-24 15:12:58 -07:00
if (error && error.reason !== BoxError.NOT_FOUND) {
console.error(error);
return callback();
}
if (error) {
2019-08-30 19:11:27 +02:00
debug('[adding user] ', user.uid, user.mail, user.cn);
users.create(user.uid, null, user.mail, user.cn, { source: 'ldap' }, auditsource.EXTERNAL_LDAP_TASK, function (error) {
if (error) console.error('Failed to create user', user, error);
callback();
});
} else if (result.source !== 'ldap') {
2019-08-30 19:11:27 +02:00
debug('[conflicting user]', user.uid, user.mail, user.cn);
callback();
} else if (result.email !== user.mail || result.displayName !== user.cn) {
2019-08-30 19:11:27 +02:00
debug('[updating user] ', user.uid, user.mail, user.cn);
2019-08-30 13:10:49 +02:00
users.update(result.id, { email: user.mail, fallbackEmail: user.mail, displayName: user.cn }, auditsource.EXTERNAL_LDAP_TASK, function (error) {
if (error) console.error('Failed to update user', user, error);
callback();
});
} else {
// user known and up-to-date
callback();
}
});
2019-08-30 19:11:27 +02:00
}, function () {
debug('User sync done.');
callback();
});
2019-08-29 17:19:51 +02:00
});
});
});
});
}