diff --git a/dashboard/src/js/client.js b/dashboard/src/js/client.js index bec71cf83..889929ce6 100644 --- a/dashboard/src/js/client.js +++ b/dashboard/src/js/client.js @@ -1040,7 +1040,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }; Client.prototype.setExternalLdapConfig = function (config, callback) { - post('/api/v1/settings/external_ldap_config', config, null, function (error, data, status) { + post('/api/v1/external_ldap/config', config, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); @@ -1049,7 +1049,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }; Client.prototype.getExternalLdapConfig = function (callback) { - get('/api/v1/settings/external_ldap_config', null, function (error, data, status) { + get('/api/v1/external_ldap/config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); @@ -2424,7 +2424,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }; Client.prototype.startExternalLdapSync = function (callback) { - post('/api/v1/cloudron/sync_external_ldap', {}, null, function (error, data, status) { + post('/api/v1/external_ldap/sync', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); diff --git a/src/externalldap.js b/src/externalldap.js index 72d28454c..ffcb33081 100644 --- a/src/externalldap.js +++ b/src/externalldap.js @@ -1,13 +1,14 @@ 'use strict'; exports = module.exports = { + getConfig, + setConfig, + verifyPassword, maybeCreateUser, - testConfig, startSyncer, - injectPrivateFields, removePrivateFields, sync @@ -58,25 +59,49 @@ function validUserRequirements(user) { } } +async function getConfig() { + const value = await settings.get(settings.EXTERNAL_LDAP_KEY); + if (value === null) return { provider: 'noop', autoCreate: false }; + + const config = JSON.parse(value); + if (!config.autoCreate) config.autoCreate = false; // ensure new keys + return config; +} + +async function setConfig(newConfig) { + assert.strictEqual(typeof newConfig, 'object'); + + if (settings.isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); + + const currentConfig = await getConfig(); + + injectPrivateFields(newConfig, currentConfig); + + const error = await testConfig(newConfig); + if (error) throw error; + + await settings.set(settings.EXTERNAL_LDAP_KEY, JSON.stringify(newConfig)); +} + // performs service bind if required -async function getClient(externalLdapConfig, options) { - assert.strictEqual(typeof externalLdapConfig, 'object'); +async function getClient(config, options) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof options, 'object'); // basic validation to not crash - try { ldap.parseDN(externalLdapConfig.baseDn); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, 'invalid baseDn'); } - try { ldap.parseFilter(externalLdapConfig.filter); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, 'invalid filter'); } - - const config = { - url: externalLdapConfig.url, - tlsOptions: { - rejectUnauthorized: externalLdapConfig.acceptSelfSignedCerts ? false : true - } - }; + try { ldap.parseDN(config.baseDn); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, 'invalid baseDn'); } + try { ldap.parseFilter(config.filter); } catch (e) { throw new BoxError(BoxError.BAD_FIELD, 'invalid filter'); } let client; try { - client = ldap.createClient(config); + const ldapConfig = { + url: config.url, + tlsOptions: { + rejectUnauthorized: config.acceptSelfSignedCerts ? false : true + } + }; + + client = ldap.createClient(ldapConfig); } catch (e) { if (e instanceof ldap.ProtocolError) throw new BoxError(BoxError.BAD_FIELD, 'url protocol is invalid'); throw new BoxError(BoxError.INTERNAL_ERROR, e); @@ -92,9 +117,9 @@ async function getClient(externalLdapConfig, options) { }); // skip bind auth if none exist or if not wanted - if (!externalLdapConfig.bindDn || !options.bind) return resolve(client); + if (!config.bindDn || !options.bind) return resolve(client); - client.bind(externalLdapConfig.bindDn, externalLdapConfig.bindPassword, function (error) { + client.bind(config.bindDn, config.bindPassword, function (error) { if (error instanceof ldap.InvalidCredentialsError) return reject(new BoxError(BoxError.INVALID_CREDENTIALS)); if (error) return reject(new BoxError(BoxError.EXTERNAL_ERROR, error)); @@ -130,8 +155,8 @@ async function clientSearch(client, dn, searchOptions) { }); } -async function ldapGetByDN(externalLdapConfig, dn) { - assert.strictEqual(typeof externalLdapConfig, 'object'); +async function ldapGetByDN(config, dn) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof dn, 'string'); const searchOptions = { @@ -141,7 +166,7 @@ async function ldapGetByDN(externalLdapConfig, dn) { debug(`ldapGetByDN: Get object at ${dn}`); - const client = await getClient(externalLdapConfig, { bind: true }); + const client = await getClient(config, { bind: true }); const result = await clientSearch(client, dn, searchOptions); client.unbind(); if (result.length === 0) throw new BoxError(BoxError.NOT_FOUND); @@ -149,13 +174,13 @@ async function ldapGetByDN(externalLdapConfig, dn) { } // TODO support search by email -async function ldapUserSearch(externalLdapConfig, options) { - assert.strictEqual(typeof externalLdapConfig, 'object'); +async function ldapUserSearch(config, options) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof options, 'object'); const searchOptions = { paged: true, - filter: ldap.parseFilter(externalLdapConfig.filter), + filter: ldap.parseFilter(config.filter), scope: 'sub' // We may have to make this configurable }; @@ -164,14 +189,14 @@ async function ldapUserSearch(externalLdapConfig, options) { searchOptions.filter = new ldap.AndFilter({ filters: [ extraFilter, searchOptions.filter ] }); } - const client = await getClient(externalLdapConfig, { bind: true }); - const result = await clientSearch(client, externalLdapConfig.baseDn, searchOptions); + const client = await getClient(config, { bind: true }); + const result = await clientSearch(client, config.baseDn, searchOptions); client.unbind(); return result; } -async function ldapGroupSearch(externalLdapConfig, options) { - assert.strictEqual(typeof externalLdapConfig, 'object'); +async function ldapGroupSearch(config, options) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof options, 'object'); const searchOptions = { @@ -179,15 +204,15 @@ async function ldapGroupSearch(externalLdapConfig, options) { scope: 'sub' // We may have to make this configurable }; - if (externalLdapConfig.groupFilter) searchOptions.filter = ldap.parseFilter(externalLdapConfig.groupFilter); + if (config.groupFilter) searchOptions.filter = ldap.parseFilter(config.groupFilter); if (options.filter) { // https://github.com/ldapjs/node-ldapjs/blob/master/docs/filters.md const extraFilter = ldap.parseFilter(options.filter); searchOptions.filter = new ldap.AndFilter({ filters: [ extraFilter, searchOptions.filter ] }); } - const client = await getClient(externalLdapConfig, { bind: true }); - const result = await clientSearch(client, externalLdapConfig.groupBaseDn, searchOptions); + const client = await getClient(config, { bind: true }); + const result = await clientSearch(client, config.groupBaseDn, searchOptions); client.unbind(); return result; } @@ -239,15 +264,15 @@ async function testConfig(config) { async function maybeCreateUser(identifier) { assert.strictEqual(typeof identifier, 'string'); - const externalLdapConfig = await settings.getExternalLdapConfig(); - if (externalLdapConfig.provider === 'noop') throw new BoxError(BoxError.BAD_STATE, 'not enabled'); - if (!externalLdapConfig.autoCreate) throw new BoxError(BoxError.BAD_STATE, 'auto create not enabled'); + const config = await getConfig(); + if (config.provider === 'noop') throw new BoxError(BoxError.BAD_STATE, 'not enabled'); + if (!config.autoCreate) throw new BoxError(BoxError.BAD_STATE, 'auto create not enabled'); - const ldapUsers = await ldapUserSearch(externalLdapConfig, { filter: `${externalLdapConfig.usernameField}=${identifier}` }); + const ldapUsers = await ldapUserSearch(config, { filter: `${config.usernameField}=${identifier}` }); if (ldapUsers.length === 0) throw new BoxError(BoxError.NOT_FOUND); if (ldapUsers.length > 1) throw new BoxError(BoxError.CONFLICT); - const user = translateUser(externalLdapConfig, ldapUsers[0]); + const user = translateUser(config, ldapUsers[0]); if (!validUserRequirements(user)) throw new BoxError(BoxError.BAD_FIELD); return await users.add(user.email, { username: user.username, password: null, displayName: user.displayName, source: 'ldap' }, AuditSource.EXTERNAL_LDAP_AUTO_CREATE); @@ -258,14 +283,14 @@ async function verifyPassword(user, password, totpToken) { assert.strictEqual(typeof password, 'string'); assert(totpToken === null || typeof totpToken === 'string'); - const externalLdapConfig = await settings.getExternalLdapConfig(); - if (externalLdapConfig.provider === 'noop') throw new BoxError(BoxError.BAD_STATE, 'not enabled'); + const config = await getConfig(); + if (config.provider === 'noop') throw new BoxError(BoxError.BAD_STATE, 'not enabled'); - const ldapUsers = await ldapUserSearch(externalLdapConfig, { filter: `${externalLdapConfig.usernameField}=${user.username}` }); + const ldapUsers = await ldapUserSearch(config, { filter: `${config.usernameField}=${user.username}` }); if (ldapUsers.length === 0) throw new BoxError(BoxError.NOT_FOUND); if (ldapUsers.length > 1) throw new BoxError(BoxError.CONFLICT); - const client = await getClient(externalLdapConfig, { bind: false }); + const client = await getClient(config, { bind: false }); let userAuthDn; if (totpToken) { @@ -281,12 +306,12 @@ async function verifyPassword(user, password, totpToken) { if (error instanceof ldap.InvalidCredentialsError) throw new BoxError(BoxError.INVALID_CREDENTIALS); if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, error); - return translateUser(externalLdapConfig, ldapUsers[0]); + return translateUser(config, ldapUsers[0]); } async function startSyncer() { - const externalLdapConfig = await settings.getExternalLdapConfig(); - if (externalLdapConfig.provider === 'noop') throw new BoxError(BoxError.BAD_STATE, 'not enabled'); + const config = await getConfig(); + if (config.provider === 'noop') throw new BoxError(BoxError.BAD_STATE, 'not enabled'); const taskId = await tasks.add(tasks.TASK_SYNC_EXTERNAL_LDAP, []); @@ -297,11 +322,11 @@ async function startSyncer() { return taskId; } -async function syncUsers(externalLdapConfig, progressCallback) { - assert.strictEqual(typeof externalLdapConfig, 'object'); +async function syncUsers(config, progressCallback) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - const ldapUsers = await ldapUserSearch(externalLdapConfig, {}); + const ldapUsers = await ldapUserSearch(config, {}); debug(`syncUsers: Found ${ldapUsers.length} users`); @@ -310,7 +335,7 @@ async function syncUsers(externalLdapConfig, progressCallback) { // we ignore all errors here and just log them for now for (let i = 0; i < ldapUsers.length; i++) { - let ldapUser = translateUser(externalLdapConfig, ldapUsers[i]); + let ldapUser = translateUser(config, ldapUsers[i]); if (!validUserRequirements(ldapUser)) continue; percent += step; @@ -342,17 +367,17 @@ async function syncUsers(externalLdapConfig, progressCallback) { debug('syncUsers: done'); } -async function syncGroups(externalLdapConfig, progressCallback) { - assert.strictEqual(typeof externalLdapConfig, 'object'); +async function syncGroups(config, progressCallback) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - if (!externalLdapConfig.syncGroups) { + if (!config.syncGroups) { debug('syncGroups: Group sync is disabled'); progressCallback({ percent: 70, message: 'Skipping group sync...' }); return []; } - const ldapGroups = await ldapGroupSearch(externalLdapConfig, {}); + const ldapGroups = await ldapGroupSearch(config, {}); debug(`syncGroups: Found ${ldapGroups.length} groups`); @@ -361,7 +386,7 @@ async function syncGroups(externalLdapConfig, progressCallback) { // we ignore all non internal errors here and just log them for now for (const ldapGroup of ldapGroups) { - let groupName = ldapGroup[externalLdapConfig.groupnameField]; + let groupName = ldapGroup[config.groupnameField]; if (!groupName) return; // some servers return empty array for unknown properties :-/ if (typeof groupName !== 'string') return; @@ -387,11 +412,11 @@ async function syncGroups(externalLdapConfig, progressCallback) { debug('syncGroups: sync done'); } -async function syncGroupUsers(externalLdapConfig, progressCallback) { - assert.strictEqual(typeof externalLdapConfig, 'object'); +async function syncGroupUsers(config, progressCallback) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof progressCallback, 'function'); - if (!externalLdapConfig.syncGroups) { + if (!config.syncGroups) { debug('syncGroupUsers: Group users sync is disabled'); progressCallback({ percent: 99, message: 'Skipping group users sync...' }); return []; @@ -404,7 +429,7 @@ async function syncGroupUsers(externalLdapConfig, progressCallback) { for (const group of ldapGroups) { debug(`syncGroupUsers: Sync users for group ${group.name}`); - const result = await ldapGroupSearch(externalLdapConfig, {}); + const result = await ldapGroupSearch(config, {}); if (!result || result.length === 0) { debug(`syncGroupUsers: Unable to find group ${group.name} ignoring for now.`); continue; @@ -412,8 +437,8 @@ async function syncGroupUsers(externalLdapConfig, progressCallback) { // since our group names are lowercase we cannot use potentially case matching ldap filters let found = result.find(function (r) { - if (!r[externalLdapConfig.groupnameField]) return false; - return r[externalLdapConfig.groupnameField].toLowerCase() === group.name; + if (!r[config.groupnameField]) return false; + return r[config.groupnameField].toLowerCase() === group.name; }); if (!found) { @@ -429,7 +454,7 @@ async function syncGroupUsers(externalLdapConfig, progressCallback) { debug(`syncGroupUsers: Group ${group.name} has ${ldapGroupMembers.length} members.`); for (const memberDn of ldapGroupMembers) { - const [ldapError, result] = await safe(ldapGetByDN(externalLdapConfig, memberDn)); + const [ldapError, result] = await safe(ldapGetByDN(config, memberDn)); if (ldapError) { debug(`syncGroupUsers: Failed to get ${memberDn}: %o`, ldapError); continue; @@ -437,7 +462,7 @@ async function syncGroupUsers(externalLdapConfig, progressCallback) { debug(`syncGroupUsers: Found member object at ${memberDn} adding to group ${group.name}`); - const username = result[externalLdapConfig.usernameField].toLowerCase(); + const username = result[config.usernameField].toLowerCase(); if (!username) continue; const [getError, userObject] = await safe(users.getByUsername(username)); @@ -459,12 +484,12 @@ async function sync(progressCallback) { progressCallback({ percent: 10, message: 'Starting ldap user sync' }); - const externalLdapConfig = await settings.getExternalLdapConfig(); - if (externalLdapConfig.provider === 'noop') throw new BoxError(BoxError.BAD_STATE, 'not enabled'); + const config = await getConfig(); + if (config.provider === 'noop') throw new BoxError(BoxError.BAD_STATE, 'not enabled'); - await syncUsers(externalLdapConfig, progressCallback); - await syncGroups(externalLdapConfig, progressCallback); - await syncGroupUsers(externalLdapConfig, progressCallback); + await syncUsers(config, progressCallback); + await syncGroups(config, progressCallback); + await syncGroupUsers(config, progressCallback); progressCallback({ percent: 100, message: 'Done' }); diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index 318b21f00..90c41adf7 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -24,7 +24,6 @@ exports = module.exports = { getServerIpv4, getServerIpv6, getLanguages, - syncExternalLdap, syncDnsRecords, getSystemGraphs, getPlatformStatus, @@ -38,7 +37,6 @@ const assert = require('assert'), constants = require('../constants.js'), debug = require('debug')('box:routes/cloudron'), eventlog = require('../eventlog.js'), - externalLdap = require('../externalldap.js'), graphs = require('../graphs.js'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, @@ -311,13 +309,6 @@ async function renewCerts(req, res, next) { next(new HttpSuccess(202, { taskId })); } -async function syncExternalLdap(req, res, next) { - const [error, taskId] = await safe(externalLdap.startSyncer()); - if (error) return next(new HttpError(500, error.message)); - - next(new HttpSuccess(202, { taskId })); -} - async function getServerIpv4(req, res, next) { const [error, ipv4] = await safe(sysinfo.getServerIPv4()); if (error) return next(BoxError.toHttpError(error)); diff --git a/src/routes/externalldap.js b/src/routes/externalldap.js new file mode 100644 index 000000000..63a7cc284 --- /dev/null +++ b/src/routes/externalldap.js @@ -0,0 +1,46 @@ +'use strict'; + +exports = module.exports = { + getConfig, + setConfig, + sync +}; + +const assert = require('assert'), + BoxError = require('../boxerror.js'), + externalLdap = require('../externalldap.js'), + HttpError = require('connect-lastmile').HttpError, + HttpSuccess = require('connect-lastmile').HttpSuccess, + safe = require('safetydance'); + +async function sync(req, res, next) { + const [error, taskId] = await safe(externalLdap.startSyncer()); + if (error) return next(new HttpError(500, error.message)); + + next(new HttpSuccess(202, { taskId })); +} + +async function getConfig(req, res, next) { + const [error, config] = await safe(externalLdap.getConfig()); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, externalLdap.removePrivateFields(config))); +} + +async function setConfig(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + + if (!req.body.provider || typeof req.body.provider !== 'string') return next(new HttpError(400, 'provider must be a string')); + if ('url' in req.body && typeof req.body.url !== 'string') return next(new HttpError(400, 'url must be a string')); + if ('baseDn' in req.body && typeof req.body.baseDn !== 'string') return next(new HttpError(400, 'baseDn must be a string')); + if ('usernameField' in req.body && typeof req.body.usernameField !== 'string') return next(new HttpError(400, 'usernameField must be a string')); + if ('filter' in req.body && typeof req.body.filter !== 'string') return next(new HttpError(400, 'filter must be a string')); + if ('groupBaseDn' in req.body && typeof req.body.groupBaseDn !== 'string') return next(new HttpError(400, 'groupBaseDn must be a string')); + if ('bindDn' in req.body && typeof req.body.bindDn !== 'string') return next(new HttpError(400, 'bindDn must be a non empty string')); + if ('bindPassword' in req.body && typeof req.body.bindPassword !== 'string') return next(new HttpError(400, 'bindPassword must be a string')); + + const [error] = await safe(externalLdap.setConfig(req.body)); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, {})); +} diff --git a/src/routes/index.js b/src/routes/index.js index 4581883be..91245b3dc 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -11,6 +11,7 @@ exports = module.exports = { cloudron: require('./cloudron.js'), domains: require('./domains.js'), eventlog: require('./eventlog.js'), + externalLdap: require('./externalldap.js'), filemanager: require('./filemanager.js'), groups: require('./groups.js'), mail: require('./mail.js'), diff --git a/src/routes/settings.js b/src/routes/settings.js index d357d3f91..eb822bc29 100644 --- a/src/routes/settings.js +++ b/src/routes/settings.js @@ -12,7 +12,6 @@ const assert = require('assert'), backups = require('../backups.js'), BoxError = require('../boxerror.js'), docker = require('../docker.js'), - externalLdap = require('../externalldap.js'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, safe = require('safetydance'), @@ -110,31 +109,6 @@ async function setBackupConfig(req, res, next) { next(new HttpSuccess(200, {})); } -async function getExternalLdapConfig(req, res, next) { - const [error, config] = await safe(settings.getExternalLdapConfig()); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(200, externalLdap.removePrivateFields(config))); -} - -async function setExternalLdapConfig(req, res, next) { - assert.strictEqual(typeof req.body, 'object'); - - if (!req.body.provider || typeof req.body.provider !== 'string') return next(new HttpError(400, 'provider must be a string')); - if ('url' in req.body && typeof req.body.url !== 'string') return next(new HttpError(400, 'url must be a string')); - if ('baseDn' in req.body && typeof req.body.baseDn !== 'string') return next(new HttpError(400, 'baseDn must be a string')); - if ('usernameField' in req.body && typeof req.body.usernameField !== 'string') return next(new HttpError(400, 'usernameField must be a string')); - if ('filter' in req.body && typeof req.body.filter !== 'string') return next(new HttpError(400, 'filter must be a string')); - if ('groupBaseDn' in req.body && typeof req.body.groupBaseDn !== 'string') return next(new HttpError(400, 'groupBaseDn must be a string')); - if ('bindDn' in req.body && typeof req.body.bindDn !== 'string') return next(new HttpError(400, 'bindDn must be a non empty string')); - if ('bindPassword' in req.body && typeof req.body.bindPassword !== 'string') return next(new HttpError(400, 'bindPassword must be a string')); - - const [error] = await safe(settings.setExternalLdapConfig(req.body)); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(200, {})); -} - async function getDirectoryServerConfig(req, res, next) { const [error, config] = await safe(settings.getDirectoryServerConfig()); if (error) return next(BoxError.toHttpError(error)); @@ -278,7 +252,6 @@ function get(req, res, next) { case settings.BACKUP_POLICY_KEY: return getBackupPolicy(req, res, next); case settings.IPV6_CONFIG_KEY: return getIPv6Config(req, res, next); case settings.BACKUP_CONFIG_KEY: return getBackupConfig(req, res, next); - case settings.EXTERNAL_LDAP_KEY: return getExternalLdapConfig(req, res, next); case settings.DIRECTORY_SERVER_KEY: return getDirectoryServerConfig(req, res, next); case settings.REGISTRY_CONFIG_KEY: return getRegistryConfig(req, res, next); case settings.SYSINFO_CONFIG_KEY: return getSysinfoConfig(req, res, next); @@ -299,7 +272,6 @@ function set(req, res, next) { switch (req.params.setting) { case settings.BACKUP_POLICY_KEY: return setBackupPolicy(req, res, next); case settings.IPV6_CONFIG_KEY: return setIPv6Config(req, res, next); - case settings.EXTERNAL_LDAP_KEY: return setExternalLdapConfig(req, res, next); case settings.DIRECTORY_SERVER_KEY: return setDirectoryServerConfig(req, res, next); case settings.REGISTRY_CONFIG_KEY: return setRegistryConfig(req, res, next); case settings.SYSINFO_CONFIG_KEY: return setSysinfoConfig(req, res, next); diff --git a/src/server.js b/src/server.js index 0091d355d..3526cc78c 100644 --- a/src/server.js +++ b/src/server.js @@ -127,7 +127,6 @@ async function initializeExpressSync() { router.get ('/api/v1/cloudron/logstream/:unit', token, authorizeAdmin, routes.cloudron.getLogStream); router.get ('/api/v1/cloudron/eventlog', token, authorizeAdmin, routes.eventlog.list); router.get ('/api/v1/cloudron/eventlog/:eventId', token, authorizeAdmin, routes.eventlog.get); - router.post('/api/v1/cloudron/sync_external_ldap', json, token, authorizeAdmin, routes.cloudron.syncExternalLdap); router.get ('/api/v1/cloudron/server_ipv4', token, authorizeAdmin, routes.cloudron.getServerIpv4); router.get ('/api/v1/cloudron/server_ipv6', token, authorizeAdmin, routes.cloudron.getServerIpv6); @@ -203,6 +202,11 @@ async function initializeExpressSync() { router.post('/api/v1/groups/:groupId', json, token, authorizeUserManager, routes.groups.update); router.del ('/api/v1/groups/:groupId', token, authorizeUserManager, routes.groups.remove); + // External LDAP + router.get ('/api/v1/external_ldap/config', token, authorizeAdmin, routes.externalLdap.getConfig); + router.post('/api/v1/external_ldap/config', json, token, authorizeAdmin, routes.externalLdap.setConfig); + router.post('/api/v1/external_ldap/sync', json, token, authorizeAdmin, routes.externalLdap.sync); + // appstore and subscription routes router.post('/api/v1/appstore/register_cloudron', json, token, authorizeOwner, routes.appstore.registerCloudron); router.get ('/api/v1/appstore/web_token', json, token, authorizeOwner, routes.appstore.getWebToken); diff --git a/src/settings.js b/src/settings.js index a66012a62..9b5fe50c5 100644 --- a/src/settings.js +++ b/src/settings.js @@ -19,9 +19,6 @@ exports = module.exports = { getServicesConfig, setServicesConfig, - getExternalLdapConfig, - setExternalLdapConfig, - getDirectoryServerConfig, setDirectoryServerConfig, @@ -126,7 +123,6 @@ const assert = require('assert'), debug = require('debug')('box:settings'), directoryServer = require('./directoryserver.js'), docker = require('./docker.js'), - externalLdap = require('./externalldap.js'), moment = require('moment-timezone'), mounts = require('./mounts.js'), paths = require('./paths.js'), @@ -160,10 +156,6 @@ const gDefaults = (function () { schedule: '00 00 23 * * *' // every day at 11pm }; result[exports.SERVICES_CONFIG_KEY] = {}; - result[exports.EXTERNAL_LDAP_KEY] = { - provider: 'noop', - autoCreate: false - }; result[exports.DIRECTORY_SERVER_KEY] = { enabled: false, secret: '', @@ -376,32 +368,6 @@ async function setServicesConfig(platformConfig) { notifyChange(exports.SERVICES_CONFIG_KEY, platformConfig); } -async function getExternalLdapConfig() { - const value = await get(exports.EXTERNAL_LDAP_KEY); - if (value === null) return gDefaults[exports.EXTERNAL_LDAP_KEY]; - - const config = JSON.parse(value); - if (!config.autoCreate) config.autoCreate = false; // ensure new keys - return config; -} - -async function setExternalLdapConfig(externalLdapConfig) { - assert.strictEqual(typeof externalLdapConfig, 'object'); - - if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); - - const currentConfig = await getExternalLdapConfig(); - - externalLdap.injectPrivateFields(externalLdapConfig, currentConfig); - - const error = await externalLdap.testConfig(externalLdapConfig); - if (error) throw error; - - await set(exports.EXTERNAL_LDAP_KEY, JSON.stringify(externalLdapConfig)); - - notifyChange(exports.EXTERNAL_LDAP_KEY, externalLdapConfig); -} - async function getDirectoryServerConfig() { const value = await get(exports.DIRECTORY_SERVER_KEY); if (value === null) return gDefaults[exports.DIRECTORY_SERVER_KEY]; @@ -534,7 +500,7 @@ async function list() { // convert JSON objects [exports.BACKUP_POLICY_KEY, exports.BACKUP_CONFIG_KEY, exports.IPV6_CONFIG_KEY, exports.PROFILE_CONFIG_KEY, exports.SERVICES_CONFIG_KEY, - exports.EXTERNAL_LDAP_KEY, exports.REGISTRY_CONFIG_KEY, exports.SYSINFO_CONFIG_KEY, exports.REVERSE_PROXY_CONFIG_KEY ].forEach(function (key) { + exports.REGISTRY_CONFIG_KEY, exports.SYSINFO_CONFIG_KEY, exports.REVERSE_PROXY_CONFIG_KEY ].forEach(function (key) { result[key] = typeof result[key] === 'object' ? result[key] : safe.JSON.parse(result[key]); }); diff --git a/src/test/externalldap-test.js b/src/test/externalldap-test.js index 3d0c4eee7..e1a5267e6 100644 --- a/src/test/externalldap-test.js +++ b/src/test/externalldap-test.js @@ -14,7 +14,6 @@ const async = require('async'), ldap = require('ldapjs'), safe = require('safetydance'), server = require('../server.js'), - settings = require('../settings.js'), superagent = require('superagent'), users = require('../users.js'); @@ -195,7 +194,7 @@ describe('External LDAP', function () { let conf = Object.assign({}, LDAP_CONFIG); delete conf.url; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -203,7 +202,7 @@ describe('External LDAP', function () { let conf = Object.assign({}, LDAP_CONFIG); conf.url = ''; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -211,7 +210,7 @@ describe('External LDAP', function () { let conf = Object.assign({}, LDAP_CONFIG); delete conf.baseDn; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -219,7 +218,7 @@ describe('External LDAP', function () { let conf = Object.assign({}, LDAP_CONFIG); conf.baseDn = ''; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -227,7 +226,7 @@ describe('External LDAP', function () { let conf = Object.assign({}, LDAP_CONFIG); delete conf.filter; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -235,16 +234,16 @@ describe('External LDAP', function () { let conf = Object.assign({}, LDAP_CONFIG); conf.filter = ''; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); it('enabling succeeds', async function () { - await settings.setExternalLdapConfig(LDAP_CONFIG); + await externalLdap.setConfig(LDAP_CONFIG); }); it('disabling succeeds', async function () { - await settings.setExternalLdapConfig({ provider: 'noop' }); + await externalLdap.setConfig({ provider: 'noop' }); }); // now test with groups @@ -253,7 +252,7 @@ describe('External LDAP', function () { conf.syncGroups = true; delete conf.groupBaseDn; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -262,7 +261,7 @@ describe('External LDAP', function () { conf.syncGroups = true; conf.groupBaseDn = ''; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -271,7 +270,7 @@ describe('External LDAP', function () { conf.syncGroups = true; delete conf.groupFilter; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -280,7 +279,7 @@ describe('External LDAP', function () { conf.syncGroups = true; conf.groupFilter = ''; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -289,7 +288,7 @@ describe('External LDAP', function () { conf.syncGroups = true; delete conf.groupnameField; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -298,7 +297,7 @@ describe('External LDAP', function () { conf.syncGroups = true; conf.groupnameField = ''; - const [error] = await safe(settings.setExternalLdapConfig(conf)); + const [error] = await safe(externalLdap.setConfig(conf)); expect(error.reason).to.equal(BoxError.BAD_FIELD); }); @@ -306,13 +305,13 @@ describe('External LDAP', function () { let conf = Object.assign({}, LDAP_CONFIG); conf.syncGroups = true; - await settings.setExternalLdapConfig(conf); + await externalLdap.setConfig(conf); }); }); describe('sync', function () { it('disable sync', async function () { - await settings.setExternalLdapConfig({ provider: 'noop' }); + await externalLdap.setConfig({ provider: 'noop' }); }); it('fails if disabled', async function () { @@ -321,7 +320,7 @@ describe('External LDAP', function () { }); it('enable', async function () { - await settings.setExternalLdapConfig(LDAP_CONFIG); + await externalLdap.setConfig(LDAP_CONFIG); }); it('succeeds for new users', async function () { @@ -376,7 +375,7 @@ describe('External LDAP', function () { it('enable with groupSync', async function () { let conf = Object.assign({}, LDAP_CONFIG); conf.syncGroups = true; - await settings.setExternalLdapConfig(conf); + await externalLdap.setConfig(conf); }); it('succeeds with groups enabled', async function () { @@ -474,7 +473,7 @@ describe('External LDAP', function () { after(server.stop); it('enable', async function () { - await settings.setExternalLdapConfig(LDAP_CONFIG); + await externalLdap.setConfig(LDAP_CONFIG); }); it('fails if auto create is disabled', async function () { @@ -499,7 +498,7 @@ describe('External LDAP', function () { it('enable auto create', async function () { let conf = Object.assign({}, LDAP_CONFIG); conf.autoCreate = true; - await settings.setExternalLdapConfig(conf); + await externalLdap.setConfig(conf); }); it('fails for unknown user', async function () {