diff --git a/src/network.js b/src/network.js index 00903b7d8..afb2d8e83 100644 --- a/src/network.js +++ b/src/network.js @@ -30,6 +30,7 @@ async function setBlocklist(blocklist, auditSource) { for (const line of blocklist.split('\n')) { if (!line || line.startsWith('#')) continue; const rangeOrIP = line.trim(); + // this checks for IPv4 and IPv6 if (!validator.isIP(rangeOrIP) && !validator.isIPRange(rangeOrIP)) throw new BoxError(BoxError.BAD_FIELD, `${rangeOrIP} is not a valid IP or range`); if (rangeOrIP.indexOf('/') === -1) { diff --git a/src/settings.js b/src/settings.js index 6c3da6125..9bf4d6ac9 100644 --- a/src/settings.js +++ b/src/settings.js @@ -150,20 +150,17 @@ const assert = require('assert'), externalLdap = require('./externalldap.js'), moment = require('moment-timezone'), mounts = require('./mounts.js'), - path = require('path'), paths = require('./paths.js'), safe = require('safetydance'), - shell = require('./shell.js'), sysinfo = require('./sysinfo.js'), tokens = require('./tokens.js'), translation = require('./translation.js'), + userdirectory = require('./userdirectory.js'), users = require('./users.js'), - validator = require('validator'), _ = require('underscore'); const SETTINGS_FIELDS = [ 'name', 'value' ].join(','); const SETTINGS_BLOB_FIELDS = [ 'name', 'valueBlob' ].join(','); -const SET_LDAP_ALLOWLIST_CMD = path.join(__dirname, 'scripts/setldapallowlist.sh'); const gDefaults = (function () { const result = { }; @@ -547,32 +544,9 @@ async function setUserDirectoryConfig(userDirectoryConfig) { allowlist: userDirectoryConfig.allowlist || '' }; - if (config.enabled) { - if (!config.secret) throw new BoxError(BoxError.BAD_FIELD, 'secret cannot be empty'); - - let gotOne = false; - for (const line of userDirectoryConfig.allowlist.split('\n')) { - if (!line || line.startsWith('#')) continue; - const rangeOrIP = line.trim(); - if (!validator.isIP(rangeOrIP) && !validator.isIPRange(rangeOrIP)) throw new BoxError(BoxError.BAD_FIELD, `${rangeOrIP} is not a valid IP or range`); - gotOne = true; - } - - // only allow if we at least have one allowed IP/range - if (!gotOne) throw new BoxError(BoxError.BAD_FIELD, 'allowlist must at least contain one IP or range'); - } - + await userdirectory.validateConfig(config); await set(exports.USER_DIRECTORY_KEY, JSON.stringify(config)); - - // this is done only because it's easier for the shell script and the firewall service to get the value - if (config.enabled) { - if (!safe.fs.writeFileSync(paths.LDAP_ALLOWLIST_FILE, userDirectoryConfig.allowlist + '\n', 'utf8')) throw new BoxError(BoxError.FS_ERROR, safe.error.message); - } else { - safe.fs.unlinkSync(paths.LDAP_ALLOWLIST_FILE); - } - - const [error] = await safe(shell.promises.sudo('setLdapAllowlist', [ SET_LDAP_ALLOWLIST_CMD ], {})); - if (error) throw new BoxError(BoxError.IPTABLES_ERROR, `Error setting ldap allowlist: ${error.message}`); + await userdirectory.applyConfig(config); notifyChange(exports.USER_DIRECTORY_KEY, config); } diff --git a/src/userdirectory.js b/src/userdirectory.js index 5dbed1dac..31742de00 100644 --- a/src/userdirectory.js +++ b/src/userdirectory.js @@ -2,7 +2,10 @@ exports = module.exports = { start, - stop + stop, + + validateConfig, + applyConfig }; const assert = require('assert'), @@ -15,11 +18,15 @@ const assert = require('assert'), fs = require('fs'), groups = require('./groups.js'), ldap = require('ldapjs'), + path = require('path'), + paths = require('./paths.js'), reverseproxy = require('./reverseproxy.js'), safe = require('safetydance'), settings = require('./settings.js'), + shell = require('./shell.js'), users = require('./users.js'), - util = require('util'); + util = require('util'), + validator = require('validator'); var gServer = null; @@ -27,6 +34,41 @@ const NOOP = function () {}; const GROUP_USERS_DN = 'cn=users,ou=groups,dc=cloudron'; const GROUP_ADMINS_DN = 'cn=admins,ou=groups,dc=cloudron'; +const SET_LDAP_ALLOWLIST_CMD = path.join(__dirname, 'scripts/setldapallowlist.sh'); + +async function validateConfig(config) { + const { enabled, secret, allowlist } = config; + + if (!enabled) return; + + if (!secret) throw new BoxError(BoxError.BAD_FIELD, 'secret cannot be empty'); + + let gotOne = false; + for (const line of allowlist.split('\n')) { + if (!line || line.startsWith('#')) continue; + const rangeOrIP = line.trim(); + // this checks for IPv4 and IPv6 + if (!validator.isIP(rangeOrIP) && !validator.isIPRange(rangeOrIP)) throw new BoxError(BoxError.BAD_FIELD, `${rangeOrIP} is not a valid IP or range`); + gotOne = true; + } + + // only allow if we at least have one allowed IP/range + if (!gotOne) throw new BoxError(BoxError.BAD_FIELD, 'allowlist must at least contain one IP or range'); +} + +async function applyConfig(config) { + assert.strictEqual(typeof config, 'object'); + + // this is done only because it's easier for the shell script and the firewall service to get the value + if (config.enabled) { + if (!safe.fs.writeFileSync(paths.LDAP_ALLOWLIST_FILE, config.allowlist + '\n', 'utf8')) throw new BoxError(BoxError.FS_ERROR, safe.error.message); + } else { + safe.fs.unlinkSync(paths.LDAP_ALLOWLIST_FILE); + } + + const [error] = await safe(shell.promises.sudo('setLdapAllowlist', [ SET_LDAP_ALLOWLIST_CMD ], {})); + if (error) throw new BoxError(BoxError.IPTABLES_ERROR, `Error setting ldap allowlist: ${error.message}`); +} // helper function to deal with pagination function finalSend(results, req, res, next) {