diff --git a/dashboard/src/js/client.js b/dashboard/src/js/client.js index 889929ce6..21c130926 100644 --- a/dashboard/src/js/client.js +++ b/dashboard/src/js/client.js @@ -1076,7 +1076,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }; Client.prototype.setUserDirectoryConfig = function (config, callback) { - post('/api/v1/settings/user_directory_config', config, null, function (error, data, status) { + post('/api/v1/settings/directory_server/config', config, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); @@ -1085,7 +1085,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }; Client.prototype.getUserDirectoryConfig = function (callback) { - get('/api/v1/settings/user_directory_config', null, function (error, data, status) { + get('/api/v1/settings/directory_server/config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); diff --git a/src/directoryserver.js b/src/directoryserver.js index e1e0fdf69..4e45ba7bf 100644 --- a/src/directoryserver.js +++ b/src/directoryserver.js @@ -1,13 +1,13 @@ 'use strict'; exports = module.exports = { + getConfig, + setConfig, + start, stop, checkCertificate, - - validateConfig, - applyConfig }; const assert = require('assert'), @@ -33,6 +33,17 @@ const NOOP = function () {}; const SET_LDAP_ALLOWLIST_CMD = path.join(__dirname, 'scripts/setldapallowlist.sh'); +async function getConfig() { + const value = await settings.get(settings.DIRECTORY_SERVER_KEY); + if (value === null) return { + enabled: false, + secret: '', + allowlist: '' // empty means allow all + }; + + return JSON.parse(value); +} + async function validateConfig(config) { const { enabled, secret, allowlist } = config; @@ -69,6 +80,23 @@ async function applyConfig(config) { if (config.enabled) await start(); else await stop(); } +async function setConfig(directoryServerConfig) { + assert.strictEqual(typeof directoryServerConfig, 'object'); + + if (settings.isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); + + const config = { + enabled: directoryServerConfig.enabled, + secret: directoryServerConfig.secret, + // if list is empty, we allow all IPs + allowlist: directoryServerConfig.allowlist || '' + }; + + await validateConfig(config); + await settings.set(settings.DIRECTORY_SERVER_KEY, JSON.stringify(config)); + await applyConfig(config); +} + // helper function to deal with pagination function finalSend(results, req, res, next) { let min = 0; @@ -317,10 +345,10 @@ async function start() { gServer.bind('ou=system,dc=cloudron', async function(req, res, next) { debug('system bind: %s (from %s)', req.dn.toString(), req.connection.ldap.id); - const tmp = await settings.getDirectoryServerConfig(); + const config = await getConfig(); if (!req.dn.equals(constants.USER_DIRECTORY_LDAP_DN)) return next(new ldap.InvalidCredentialsError(req.dn.toString())); - if (req.credentials !== tmp.secret) return next(new ldap.InvalidCredentialsError(req.dn.toString())); + if (req.credentials !== config.secret) return next(new ldap.InvalidCredentialsError(req.dn.toString())); req.user = { user: 'directoryServerAdmin' }; diff --git a/src/routes/directoryserver.js b/src/routes/directoryserver.js new file mode 100644 index 000000000..65f779993 --- /dev/null +++ b/src/routes/directoryserver.js @@ -0,0 +1,33 @@ +'use strict'; + +exports = module.exports = { + getConfig, + setConfig, +}; + +const assert = require('assert'), + BoxError = require('../boxerror.js'), + directoryServer = require('../directoryserver.js'), + HttpError = require('connect-lastmile').HttpError, + HttpSuccess = require('connect-lastmile').HttpSuccess, + safe = require('safetydance'); + +async function getConfig(req, res, next) { + const [error, config] = await safe(directoryServer.getConfig()); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, config)); +} + +async function setConfig(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + + if (typeof req.body.enabled !== 'boolean') return next(new HttpError(400, 'enabled must be a boolean')); + if (typeof req.body.secret !== 'string') return next(new HttpError(400, 'secret must be a string')); + if ('allowlist' in req.body && typeof req.body.allowlist !== 'string') return next(new HttpError(400, 'allowlist must be a string')); + + const [error] = await safe(directoryServer.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 91245b3dc..7fd894735 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -9,6 +9,7 @@ exports = module.exports = { backups: require('./backups.js'), branding: require('./branding.js'), cloudron: require('./cloudron.js'), + directoryServer: require('./directoryserver.js'), domains: require('./domains.js'), eventlog: require('./eventlog.js'), externalLdap: require('./externalldap.js'), diff --git a/src/routes/settings.js b/src/routes/settings.js index eb822bc29..940a95de3 100644 --- a/src/routes/settings.js +++ b/src/routes/settings.js @@ -109,26 +109,6 @@ async function setBackupConfig(req, res, next) { next(new HttpSuccess(200, {})); } -async function getDirectoryServerConfig(req, res, next) { - const [error, config] = await safe(settings.getDirectoryServerConfig()); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(200, config)); -} - -async function setDirectoryServerConfig(req, res, next) { - assert.strictEqual(typeof req.body, 'object'); - - if (typeof req.body.enabled !== 'boolean') return next(new HttpError(400, 'enabled must be a boolean')); - if (typeof req.body.secret !== 'string') return next(new HttpError(400, 'secret must be a string')); - if ('allowlist' in req.body && typeof req.body.allowlist !== 'string') return next(new HttpError(400, 'allowlist must be a string')); - - const [error] = await safe(settings.setDirectoryServerConfig(req.body)); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(200, {})); -} - async function getBackupPolicy(req, res, next) { const [error, policy] = await safe(settings.getBackupPolicy()); if (error) return next(BoxError.toHttpError(error)); @@ -252,7 +232,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.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); case settings.LANGUAGE_KEY: return getLanguage(req, res, next); @@ -272,7 +251,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.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); case settings.LANGUAGE_KEY: return setLanguage(req, res, next); diff --git a/src/routes/test/directoryserver-test.js b/src/routes/test/directoryserver-test.js new file mode 100644 index 000000000..eaffb847f --- /dev/null +++ b/src/routes/test/directoryserver-test.js @@ -0,0 +1,107 @@ +'use strict'; + +/* global it:false */ +/* global describe:false */ +/* global before:false */ +/* global after:false */ + +const common = require('./common.js'), + expect = require('expect.js'), + superagent = require('superagent'); + +describe('Directory Server API', function () { + const { setup, cleanup, serverUrl, owner } = common; + + before(setup); + after(cleanup); + + describe('user_directory_config', function () { + // keep in sync with defaults in settings.js + let defaultConfig = { + enabled: false, + secret: '', + allowlist: '' + }; + + it('can get user_directory_config (default)', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/directory_server/config`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body).to.eql(defaultConfig); + }); + + it('cannot set user_directory_config without enabled boolean', async function () { + let tmp = JSON.parse(JSON.stringify(defaultConfig)); + delete tmp.enabled; + + const response = await superagent.post(`${serverUrl}/api/v1/directory_server/config`) + .query({ access_token: owner.token }) + .send(tmp) + .ok(() => true); + + expect(response.statusCode).to.equal(400); + }); + + it('cannot set user_directory_config without secret', async function () { + let tmp = JSON.parse(JSON.stringify(defaultConfig)); + delete tmp.secret; + + const response = await superagent.post(`${serverUrl}/api/v1/directory_server/config`) + .query({ access_token: owner.token }) + .send(tmp) + .ok(() => true); + + expect(response.statusCode).to.equal(400); + }); + + it('cannot enable user_directory_config with empty secret', async function () { + let tmp = JSON.parse(JSON.stringify(defaultConfig)); + tmp.enabled = true; + + const response = await superagent.post(`${serverUrl}/api/v1/directory_server/config`) + .query({ access_token: owner.token }) + .send(tmp) + .ok(() => true); + + expect(response.statusCode).to.equal(400); + }); + + it('cannot enable user_directory_config with empty allowlist', async function () { + let tmp = JSON.parse(JSON.stringify(defaultConfig)); + tmp.enabled = true; + tmp.secret = 'ldapsecret'; + + const response = await superagent.post(`${serverUrl}/api/v1/directory_server/config`) + .query({ access_token: owner.token }) + .send(tmp) + .ok(() => true); + + expect(response.statusCode).to.equal(400); + }); + + it('can enable user_directory_config', async function () { + let tmp = JSON.parse(JSON.stringify(defaultConfig)); + tmp.enabled = true; + tmp.secret = 'ldapsecret'; + tmp.allowlist = '1.2.3.4'; + + const response = await superagent.post(`${serverUrl}/api/v1/directory_server/config`) + .query({ access_token: owner.token }) + .send(tmp); + + expect(response.statusCode).to.equal(200); + }); + + it('can get user_directory_config', async function () { + let tmp = JSON.parse(JSON.stringify(defaultConfig)); + tmp.enabled = true; + + const response = await superagent.get(`${serverUrl}/api/v1/directory_server/config`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body).to.eql({ enabled: true, secret: 'ldapsecret', allowlist: '1.2.3.4' }); + }); + }); +}); diff --git a/src/routes/test/externalldap-test.js b/src/routes/test/externalldap-test.js new file mode 100644 index 000000000..94433f30e --- /dev/null +++ b/src/routes/test/externalldap-test.js @@ -0,0 +1,41 @@ +'use strict'; + +/* global it:false */ +/* global describe:false */ +/* global before:false */ +/* global after:false */ + +const common = require('./common.js'), + expect = require('expect.js'), + superagent = require('superagent'); + +describe('External LDAP API', function () { + const { setup, cleanup, serverUrl, owner } = common; + + before(setup); + after(cleanup); + + describe('external_ldap', function () { + // keep in sync with defaults in settings.js + let defaultConfig = { provider: 'noop', autoCreate: false }; + + it('can get external_ldap (default)', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/external_ldap/config`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body).to.eql(defaultConfig); + }); + + it('can set external_ldap to noop', async function () { + const config = { + provider: 'noop' + }; + const response = await superagent.post(`${serverUrl}/api/v1/external_ldap/config`) + .query({ access_token: owner.token }) + .send(config); + + expect(response.statusCode).to.equal(200); + }); + }); +}); diff --git a/src/routes/test/settings-test.js b/src/routes/test/settings-test.js index d674036eb..4161bde21 100644 --- a/src/routes/test/settings-test.js +++ b/src/routes/test/settings-test.js @@ -79,96 +79,6 @@ describe('Settings API', function () { }); }); - describe('user_directory_config', function () { - // keep in sync with defaults in settings.js - let defaultConfig = { - enabled: false, - secret: '', - allowlist: '' - }; - - it('can get user_directory_config (default)', async function () { - const response = await superagent.get(`${serverUrl}/api/v1/settings/user_directory_config`) - .query({ access_token: owner.token }); - - expect(response.statusCode).to.equal(200); - expect(response.body).to.eql(defaultConfig); - }); - - it('cannot set user_directory_config without enabled boolean', async function () { - let tmp = JSON.parse(JSON.stringify(defaultConfig)); - delete tmp.enabled; - - const response = await superagent.post(`${serverUrl}/api/v1/settings/user_directory_config`) - .query({ access_token: owner.token }) - .send(tmp) - .ok(() => true); - - expect(response.statusCode).to.equal(400); - }); - - it('cannot set user_directory_config without secret', async function () { - let tmp = JSON.parse(JSON.stringify(defaultConfig)); - delete tmp.secret; - - const response = await superagent.post(`${serverUrl}/api/v1/settings/user_directory_config`) - .query({ access_token: owner.token }) - .send(tmp) - .ok(() => true); - - expect(response.statusCode).to.equal(400); - }); - - it('cannot enable user_directory_config with empty secret', async function () { - let tmp = JSON.parse(JSON.stringify(defaultConfig)); - tmp.enabled = true; - - const response = await superagent.post(`${serverUrl}/api/v1/settings/user_directory_config`) - .query({ access_token: owner.token }) - .send(tmp) - .ok(() => true); - - expect(response.statusCode).to.equal(400); - }); - - it('cannot enable user_directory_config with empty allowlist', async function () { - let tmp = JSON.parse(JSON.stringify(defaultConfig)); - tmp.enabled = true; - tmp.secret = 'ldapsecret'; - - const response = await superagent.post(`${serverUrl}/api/v1/settings/user_directory_config`) - .query({ access_token: owner.token }) - .send(tmp) - .ok(() => true); - - expect(response.statusCode).to.equal(400); - }); - - it('can enable user_directory_config', async function () { - let tmp = JSON.parse(JSON.stringify(defaultConfig)); - tmp.enabled = true; - tmp.secret = 'ldapsecret'; - tmp.allowlist = '1.2.3.4'; - - const response = await superagent.post(`${serverUrl}/api/v1/settings/user_directory_config`) - .query({ access_token: owner.token }) - .send(tmp); - - expect(response.statusCode).to.equal(200); - }); - - it('can get user_directory_config', async function () { - let tmp = JSON.parse(JSON.stringify(defaultConfig)); - tmp.enabled = true; - - const response = await superagent.get(`${serverUrl}/api/v1/settings/user_directory_config`) - .query({ access_token: owner.token }); - - expect(response.statusCode).to.equal(200); - expect(response.body).to.eql({ enabled: true, secret: 'ldapsecret', allowlist: '1.2.3.4' }); - }); - }); - describe('backup_policy', function () { const defaultPolicy = { retention: { keepWithinSecs: 2 * 24 * 60 * 60 }, // 2 days diff --git a/src/server.js b/src/server.js index 3526cc78c..ea38ae3d2 100644 --- a/src/server.js +++ b/src/server.js @@ -207,6 +207,10 @@ async function initializeExpressSync() { 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); + // Directory Server + router.get ('/api/v1/directory_server/config', token, authorizeAdmin, routes.directoryServer.getConfig); + router.post('/api/v1/directory_server/config', json, token, authorizeAdmin, routes.directoryServer.setConfig); + // 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 9b5fe50c5..970eb791e 100644 --- a/src/settings.js +++ b/src/settings.js @@ -19,9 +19,6 @@ exports = module.exports = { getServicesConfig, setServicesConfig, - getDirectoryServerConfig, - setDirectoryServerConfig, - getRegistryConfig, setRegistryConfig, @@ -121,7 +118,6 @@ const assert = require('assert'), CronJob = require('cron').CronJob, database = require('./database.js'), debug = require('debug')('box:settings'), - directoryServer = require('./directoryserver.js'), docker = require('./docker.js'), moment = require('moment-timezone'), mounts = require('./mounts.js'), @@ -156,11 +152,6 @@ const gDefaults = (function () { schedule: '00 00 23 * * *' // every day at 11pm }; result[exports.SERVICES_CONFIG_KEY] = {}; - result[exports.DIRECTORY_SERVER_KEY] = { - enabled: false, - secret: '', - allowlist: '' // empty means allow all - }; result[exports.REGISTRY_CONFIG_KEY] = { provider: 'noop' }; @@ -368,31 +359,6 @@ async function setServicesConfig(platformConfig) { notifyChange(exports.SERVICES_CONFIG_KEY, platformConfig); } -async function getDirectoryServerConfig() { - const value = await get(exports.DIRECTORY_SERVER_KEY); - if (value === null) return gDefaults[exports.DIRECTORY_SERVER_KEY]; - return JSON.parse(value); -} - -async function setDirectoryServerConfig(directoryServerConfig) { - assert.strictEqual(typeof directoryServerConfig, 'object'); - - if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); - - const config = { - enabled: directoryServerConfig.enabled, - secret: directoryServerConfig.secret, - // if list is empty, we allow all IPs - allowlist: directoryServerConfig.allowlist || '' - }; - - await directoryServer.validateConfig(config); - await set(exports.DIRECTORY_SERVER_KEY, JSON.stringify(config)); - await directoryServer.applyConfig(config); - - notifyChange(exports.DIRECTORY_SERVER_KEY, config); -} - async function getRegistryConfig() { const value = await get(exports.REGISTRY_CONFIG_KEY); if (value === null) return gDefaults[exports.REGISTRY_CONFIG_KEY]; diff --git a/src/test/directoryserver-test.js b/src/test/directoryserver-test.js index e82c57267..e7745be15 100644 --- a/src/test/directoryserver-test.js +++ b/src/test/directoryserver-test.js @@ -81,7 +81,7 @@ describe('User Directory Ldap', function () { async.series([ setup, directoryServer.start.bind(null), - settings.setDirectoryServerConfig.bind(null, { enabled: true, secret: auth.secret, allowlist: '127.0.0.1' }), + directoryServer.setConfig.bind(null, { enabled: true, secret: auth.secret, allowlist: '127.0.0.1' }), async () => { group = await groups.add({ name: 'ldap-test-1' }); await groups.setMembers(group.id, [ admin.id, user.id ]);