diff --git a/src/externalldap.js b/src/externalldap.js index cfd56887d..86e1a0723 100644 --- a/src/externalldap.js +++ b/src/externalldap.js @@ -4,6 +4,7 @@ exports = module.exports = { ExternalLdapError: ExternalLdapError, testConfig: testConfig, + startSyncer: startSyncer, sync: sync }; @@ -12,6 +13,7 @@ var assert = require('assert'), debug = require('debug')('box:ldapclient'), ldap = require('ldapjs'), settings = require('./settings.js'), + tasks = require('./tasks.js'), util = require('util'); function ExternalLdapError(reason, errorOrMessage) { @@ -41,30 +43,24 @@ ExternalLdapError.BAD_FIELD = 'bad field'; ExternalLdapError.NOT_FOUND = 'not found'; // performs service bind if required -function getClientAndConfig(callback) { +function getClient(externalLdapConfig, callback) { assert.strictEqual(typeof callback, 'function'); - settings.getExternalLdapConfig(function (error, externalLdapConfig) { - if (error) return callback(new ExternalLdapError(ExternalLdapError.INTERNAL_ERROR, error)); + var client; + try { + client = ldap.createClient({ url: externalLdapConfig.url }); + } catch (e) { + if (e instanceof ldap.ProtocolError) return callback(new ExternalLdapError(ExternalLdapError.BAD_FIELD, 'url protocol is invalid')); + return callback(new ExternalLdapError(ExternalLdapError.INTERNAL_ERROR, e)); + } - var client; - try { - client = ldap.createClient({ url: externalLdapConfig.url }); - } catch (e) { - if (e instanceof ldap.ProtocolError) return callback(new ExternalLdapError(ExternalLdapError.BAD_FIELD, 'url protocol is invalid')); - return callback(new ExternalLdapError(ExternalLdapError.INTERNAL_ERROR, e)); - } + if (!externalLdapConfig.bindDn) return callback(null, client); - if (!externalLdapConfig.bindDn) return callback(null, client); + client.bind(externalLdapConfig.bindDn, externalLdapConfig.bindPassword, function (error) { + if (error instanceof ldap.InvalidCredentialsError) return callback(new ExternalLdapError(ExternalLdapError.INVALID_CREDENTIALS)); + if (error) return callback(new ExternalLdapError(ExternalLdapError.EXTERNAL_ERROR, error)); - client.bind(externalLdapConfig.bindDn, externalLdapConfig.bindPassword, function (error) { - if (error instanceof ldap.InvalidCredentialsError) return callback(new ExternalLdapError(ExternalLdapError.INVALID_CREDENTIALS)); - if (error) return callback(new ExternalLdapError(ExternalLdapError.EXTERNAL_ERROR, error)); - - debug('getClient: successful'); - - callback(null, client, externalLdapConfig); - }); + callback(null, client, externalLdapConfig); }); } @@ -83,26 +79,7 @@ function testConfig(config, callback) { try { ldap.parseFilter(config.filter); } catch (e) { return callback(new ExternalLdapError(ExternalLdapError.BAD_FIELD, 'invalid filter')); } if (config.bindDn) try { ldap.parseFilter(config.bindDn); } catch (e) { return callback(new ExternalLdapError(ExternalLdapError.INVALID_CREDENTIALS)); } - var client; - try { - client = ldap.createClient({ url: config.url }); - } catch (e) { - if (e instanceof ldap.ProtocolError) return callback(new ExternalLdapError(ExternalLdapError.BAD_FIELD, 'url protocol is invalid')); - return callback(new ExternalLdapError(ExternalLdapError.EXTERNAL_ERROR, e)); - } - - function bindIfNeeded(callback) { - if (!config.bindDn) return callback(); - - client.bind(config.bindDn, config.bindPassword, function (error) { - if (error instanceof ldap.InvalidCredentialsError) return callback(new ExternalLdapError(ExternalLdapError.INVALID_CREDENTIALS)); - if (error) return callback(new ExternalLdapError(ExternalLdapError.EXTERNAL_ERROR, error)); - - callback(); - }); - } - - bindIfNeeded(function (error) { + getClient(config, function (error, client) { if (error) return callback(error); var opts = { @@ -120,31 +97,63 @@ function testConfig(config, callback) { }); } -function sync(callback) { +function startSyncer(callback) { assert.strictEqual(typeof callback, 'function'); - getClientAndConfig(function (error, client, externalLdapConfig) { - if (error) return callback(error); + settings.getExternalLdapConfig(function (error, externalLdapConfig) { + if (error) return callback(new ExternalLdapError(ExternalLdapError.INTERNAL_ERROR, error)); + if (!externalLdapConfig.enabled) return callback(new ExternalLdapError(ExternalLdapError.BAD_STATE, 'not enabled')); - var opts = { - filter: externalLdapConfig.filter, - scope: 'sub' - }; + tasks.add(tasks.TASK_SYNC_EXTERNAL_LDAP, [], function (error, taskId) { + if (error) return callback(new ExternalLdapError(ExternalLdapError.INTERNAL_ERROR, error)); - client.search(externalLdapConfig.baseDn, opts, function(err, res) { - assert.ifError(err); + tasks.startTask(taskId, {}, function (error, result) { + debug('sync: done', error, result); + }); - res.on('searchEntry', function(entry) { - console.log('entry: ' + JSON.stringify(entry.object)); - }); - res.on('searchReference', function(referral) { - console.log('referral: ' + referral.uris.join()); - }); - res.on('error', function(err) { - console.error('error: ' + err.message); - }); - res.on('end', function(result) { - console.log('status: ' + result.status); + callback(null, taskId); + }); + }); +} + +function sync(progressCallback, callback) { + assert.strictEqual(typeof progressCallback, 'function'); + assert.strictEqual(typeof callback, 'function'); + + settings.getExternalLdapConfig(function (error, externalLdapConfig) { + if (error) return callback(new ExternalLdapError(ExternalLdapError.INTERNAL_ERROR, error)); + if (!externalLdapConfig.enabled) return callback(new ExternalLdapError(ExternalLdapError.BAD_STATE, 'not enabled')); + + // basic validation to not crash + try { ldap.parseDN(externalLdapConfig.baseDn); } catch (e) { return callback(new ExternalLdapError(ExternalLdapError.BAD_FIELD, 'invalid baseDn')); } + try { ldap.parseFilter(externalLdapConfig.filter); } catch (e) { return callback(new ExternalLdapError(ExternalLdapError.BAD_FIELD, 'invalid filter')); } + if (externalLdapConfig.bindDn) try { ldap.parseFilter(externalLdapConfig.bindDn); } catch (e) { return callback(new ExternalLdapError(ExternalLdapError.INVALID_CREDENTIALS)); } + + getClient(externalLdapConfig, function (error, client) { + if (error) return callback(error); + + var opts = { + filter: externalLdapConfig.filter, + scope: 'sub' + }; + + client.search(externalLdapConfig.baseDn, opts, function (error, result) { + if (error) return callback(new ExternalLdapError(ExternalLdapError.EXTERNAL_ERROR, error)); + + result.on('searchEntry', function(entry) { + console.log('entry: ' + JSON.stringify(entry.object)); + + // TODO ensure db record for user + }); + + result.on('error', function (error) { + callback(new ExternalLdapError(ExternalLdapError.EXTERNAL_ERROR, error)); + }); + + result.on('end', function(result) { + console.log('status: ' + result.status); + callback(); + }); }); }); }); diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index c62e3c228..49827ec2f 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -12,7 +12,8 @@ exports = module.exports = { getLogStream: getLogStream, setDashboardAndMailDomain: setDashboardAndMailDomain, prepareDashboardDomain: prepareDashboardDomain, - renewCerts: renewCerts + renewCerts: renewCerts, + syncExternalLdap: syncExternalLdap }; let assert = require('assert'), @@ -22,6 +23,7 @@ let assert = require('assert'), CloudronError = cloudron.CloudronError, custom = require('../custom.js'), disks = require('../disks.js'), + externalldap = require('../externalldap.js'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, updater = require('../updater.js'), @@ -186,3 +188,11 @@ function renewCerts(req, res, next) { next(new HttpSuccess(202, { taskId })); }); } + +function syncExternalLdap(req, res, next) { + externalldap.startSyncer(function (error, taskId) { + if (error) return next(new HttpError(500, error.message)); + + next(new HttpSuccess(202, { taskId: taskId })); + }); +} diff --git a/src/server.js b/src/server.js index 8cc81d78b..219db1566 100644 --- a/src/server.js +++ b/src/server.js @@ -135,6 +135,7 @@ function initializeExpressSync() { router.get ('/api/v1/cloudron/logstream/:unit', cloudronScope, routes.cloudron.getLogStream); router.get ('/api/v1/cloudron/eventlog', cloudronScope, routes.eventlog.list); router.get ('/api/v1/cloudron/eventlog/:eventId', cloudronScope, routes.eventlog.get); + router.post('/api/v1/cloudron/sync_external_ldap', cloudronScope, routes.cloudron.syncExternalLdap); // tasks router.get ('/api/v1/tasks', settingsScope, routes.tasks.list); diff --git a/src/tasks.js b/src/tasks.js index d58f8c670..fc85a6c08 100644 --- a/src/tasks.js +++ b/src/tasks.js @@ -23,6 +23,7 @@ exports = module.exports = { TASK_RENEW_CERTS: 'renewcerts', TASK_PREPARE_DASHBOARD_DOMAIN: 'prepareDashboardDomain', TASK_CLEAN_BACKUPS: 'cleanBackups', + TASK_SYNC_EXTERNAL_LDAP: 'syncExternalLdap', // testing _TASK_IDENTITY: '_identity', diff --git a/src/taskworker.js b/src/taskworker.js index 82a20dfb8..1b755747e 100755 --- a/src/taskworker.js +++ b/src/taskworker.js @@ -9,6 +9,7 @@ var apptask = require('./apptask.js'), database = require('./database.js'), debug = require('debug')('box:taskworker'), domains = require('./domains.js'), + externalldap = require('./externalldap.js'), reverseProxy = require('./reverseproxy.js'), settings = require('./settings.js'), tasks = require('./tasks.js'), @@ -23,6 +24,7 @@ const TASKS = { // indexed by task type renewcerts: reverseProxy.renewCerts, prepareDashboardDomain: domains.prepareDashboardDomain, cleanBackups: backups.cleanup, + syncExternalLdap: externalldap.sync, _identity: (arg, progressCallback, callback) => callback(null, arg), _error: (arg, progressCallback, callback) => callback(new Error(`Failed for arg: ${arg}`)),