reorder functions for no-use-before-define
This commit is contained in:
@@ -56,49 +56,82 @@ async function validateConfig(config) {
|
||||
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');
|
||||
async function authorize(req, res, next) {
|
||||
debug('authorize: ', req.connection.ldap.bindDN.toString());
|
||||
|
||||
// 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);
|
||||
}
|
||||
// this is for connection attempts without previous bind
|
||||
if (req.connection.ldap.bindDN.equals('cn=anonymous')) return next(new ldap.InsufficientAccessRightsError());
|
||||
|
||||
const [error] = await safe(shell.sudo([ SET_LDAP_ALLOWLIST_CMD ], {}));
|
||||
if (error) throw new BoxError(BoxError.IPTABLES_ERROR, `Error setting ldap allowlist: ${error.message}`);
|
||||
// we only allow this one DN to pass
|
||||
if (!req.connection.ldap.bindDN.equals(constants.USER_DIRECTORY_LDAP_DN)) return next(new ldap.InsufficientAccessRightsError());
|
||||
|
||||
if (!config.enabled) {
|
||||
await stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gServer) await start();
|
||||
return next();
|
||||
}
|
||||
|
||||
async function setConfig(directoryServerConfig, auditSource) {
|
||||
assert.strictEqual(typeof directoryServerConfig, 'object');
|
||||
assert(auditSource && typeof auditSource === 'object');
|
||||
async function maybeRootDSE(req, res, next) {
|
||||
debug(`maybeRootDSE: requested with scope:${req.scope} dn:${req.dn.toString()}`);
|
||||
|
||||
if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode');
|
||||
if (req.scope !== 'base') return next(new ldap.NoSuchObjectError()); // per the spec, rootDSE search require base scope
|
||||
if (!req.dn || req.dn.toString() !== '') return next(new ldap.NoSuchObjectError());
|
||||
|
||||
const oldConfig = await getConfig();
|
||||
|
||||
const config = {
|
||||
enabled: directoryServerConfig.enabled,
|
||||
secret: directoryServerConfig.secret,
|
||||
allowlist: directoryServerConfig.allowlist || ''
|
||||
};
|
||||
|
||||
await validateConfig(config);
|
||||
await settings.setJson(settings.DIRECTORY_SERVER_KEY, config);
|
||||
await applyConfig(config);
|
||||
|
||||
await eventlog.add(eventlog.ACTION_DIRECTORY_SERVER_CONFIGURE, auditSource, { fromEnabled: oldConfig.enabled, toEnabled: config.enabled });
|
||||
res.send({
|
||||
dn: '',
|
||||
attributes: {
|
||||
objectclass: [ 'RootDSE', 'top', 'OpenLDAProotDSE' ],
|
||||
supportedLDAPVersion: '3',
|
||||
vendorName: 'Cloudron LDAP',
|
||||
vendorVersion: '1.0.0',
|
||||
supportedControl: [ ldap.PagedResultsControl.OID ],
|
||||
supportedExtension: []
|
||||
}
|
||||
});
|
||||
res.end();
|
||||
}
|
||||
|
||||
// helper function to deal with pagination
|
||||
async function userAuth(req, res, next) {
|
||||
// extract the common name which might have different attribute names
|
||||
const cnAttributeName = Object.keys(req.dn.rdns[0].attrs)[0];
|
||||
const commonName = req.dn.rdns[0].attrs[cnAttributeName].value;
|
||||
if (!commonName) return next(new ldap.NoSuchObjectError('Missing CN'));
|
||||
|
||||
// totptoken is passed as the "attribute" using the '+' separator in the first RDNS of the request DN
|
||||
// when totptoken attribute is present, it signals that we must enforce totp check
|
||||
// totp check is currently requested by the client. this is the only way to auth against external cloudron dashboard, external cloudron app and external apps
|
||||
const TOTPTOKEN_ATTRIBUTE_NAME = 'totptoken'; // This has to be in-sync with externalldap.js
|
||||
const totpToken = TOTPTOKEN_ATTRIBUTE_NAME in req.dn.rdns[0].attrs ? req.dn.rdns[0].attrs[TOTPTOKEN_ATTRIBUTE_NAME].value : null;
|
||||
const skipTotpCheck = !(TOTPTOKEN_ATTRIBUTE_NAME in req.dn.rdns[0].attrs);
|
||||
|
||||
let verifyFunc;
|
||||
if (cnAttributeName === 'mail') {
|
||||
verifyFunc = users.verifyWithEmail;
|
||||
} else if (commonName.indexOf('@') !== -1) { // if mail is specified, enforce mail check
|
||||
verifyFunc = users.verifyWithEmail;
|
||||
} else if (commonName.indexOf('uid-') === 0) {
|
||||
verifyFunc = users.verifyWithId;
|
||||
} else {
|
||||
verifyFunc = users.verifyWithUsername;
|
||||
}
|
||||
|
||||
const [error, user] = await safe(verifyFunc(commonName, req.credentials || '', '', { totpToken, skipTotpCheck }));
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(error.message));
|
||||
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(error.message));
|
||||
if (error) return next(new ldap.OperationsError(error.message));
|
||||
|
||||
req.user = user;
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
if (!gServer) return;
|
||||
|
||||
debug('stopping server');
|
||||
|
||||
await util.promisify(gServer.close.bind(gServer))();
|
||||
gServer = null;
|
||||
}
|
||||
|
||||
function finalSend(results, req, res, next) {
|
||||
const min = 0, max = results.length;
|
||||
let cookie = null;
|
||||
@@ -157,40 +190,7 @@ function finalSend(results, req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
async function authorize(req, res, next) {
|
||||
debug('authorize: ', req.connection.ldap.bindDN.toString());
|
||||
|
||||
// this is for connection attempts without previous bind
|
||||
if (req.connection.ldap.bindDN.equals('cn=anonymous')) return next(new ldap.InsufficientAccessRightsError());
|
||||
|
||||
// we only allow this one DN to pass
|
||||
if (!req.connection.ldap.bindDN.equals(constants.USER_DIRECTORY_LDAP_DN)) return next(new ldap.InsufficientAccessRightsError());
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
// https://ldapwiki.com/wiki/RootDSE / RFC 4512 - ldapsearch -x -h "${CLOUDRON_LDAP_SERVER}" -p "${CLOUDRON_LDAP_PORT}" -b "" -s base
|
||||
// ldapjs seems to call this handler for everything when search === ''
|
||||
async function maybeRootDSE(req, res, next) {
|
||||
debug(`maybeRootDSE: requested with scope:${req.scope} dn:${req.dn.toString()}`);
|
||||
|
||||
if (req.scope !== 'base') return next(new ldap.NoSuchObjectError()); // per the spec, rootDSE search require base scope
|
||||
if (!req.dn || req.dn.toString() !== '') return next(new ldap.NoSuchObjectError());
|
||||
|
||||
res.send({
|
||||
dn: '',
|
||||
attributes: {
|
||||
objectclass: [ 'RootDSE', 'top', 'OpenLDAProotDSE' ],
|
||||
supportedLDAPVersion: '3',
|
||||
vendorName: 'Cloudron LDAP',
|
||||
vendorVersion: '1.0.0',
|
||||
supportedControl: [ ldap.PagedResultsControl.OID ],
|
||||
supportedExtension: []
|
||||
}
|
||||
});
|
||||
res.end();
|
||||
}
|
||||
|
||||
// Will attach req.user if successful
|
||||
async function userSearch(req, res, next) {
|
||||
debug('user search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
@@ -286,41 +286,6 @@ async function groupSearch(req, res, next) {
|
||||
finalSend(results, req, res, next);
|
||||
}
|
||||
|
||||
// Will attach req.user if successful
|
||||
async function userAuth(req, res, next) {
|
||||
// extract the common name which might have different attribute names
|
||||
const cnAttributeName = Object.keys(req.dn.rdns[0].attrs)[0];
|
||||
const commonName = req.dn.rdns[0].attrs[cnAttributeName].value;
|
||||
if (!commonName) return next(new ldap.NoSuchObjectError('Missing CN'));
|
||||
|
||||
// totptoken is passed as the "attribute" using the '+' separator in the first RDNS of the request DN
|
||||
// when totptoken attribute is present, it signals that we must enforce totp check
|
||||
// totp check is currently requested by the client. this is the only way to auth against external cloudron dashboard, external cloudron app and external apps
|
||||
const TOTPTOKEN_ATTRIBUTE_NAME = 'totptoken'; // This has to be in-sync with externalldap.js
|
||||
const totpToken = TOTPTOKEN_ATTRIBUTE_NAME in req.dn.rdns[0].attrs ? req.dn.rdns[0].attrs[TOTPTOKEN_ATTRIBUTE_NAME].value : null;
|
||||
const skipTotpCheck = !(TOTPTOKEN_ATTRIBUTE_NAME in req.dn.rdns[0].attrs);
|
||||
|
||||
let verifyFunc;
|
||||
if (cnAttributeName === 'mail') {
|
||||
verifyFunc = users.verifyWithEmail;
|
||||
} else if (commonName.indexOf('@') !== -1) { // if mail is specified, enforce mail check
|
||||
verifyFunc = users.verifyWithEmail;
|
||||
} else if (commonName.indexOf('uid-') === 0) {
|
||||
verifyFunc = users.verifyWithId;
|
||||
} else {
|
||||
verifyFunc = users.verifyWithUsername;
|
||||
}
|
||||
|
||||
const [error, user] = await safe(verifyFunc(commonName, req.credentials || '', '', { totpToken, skipTotpCheck }));
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(error.message));
|
||||
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(error.message));
|
||||
if (error) return next(new ldap.OperationsError(error.message));
|
||||
|
||||
req.user = user;
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
async function start() {
|
||||
assert(gServer === null, 'Already running');
|
||||
|
||||
@@ -383,13 +348,48 @@ async function start() {
|
||||
await util.promisify(gServer.listen.bind(gServer))(constants.USER_DIRECTORY_LDAPS_PORT, '::');
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
if (!gServer) return;
|
||||
// https://ldapwiki.com/wiki/RootDSE / RFC 4512 - ldapsearch -x -h "${CLOUDRON_LDAP_SERVER}" -p "${CLOUDRON_LDAP_PORT}" -b "" -s base
|
||||
// ldapjs seems to call this handler for everything when search === ''
|
||||
async function applyConfig(config) {
|
||||
assert.strictEqual(typeof config, 'object');
|
||||
|
||||
debug('stopping server');
|
||||
// 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);
|
||||
}
|
||||
|
||||
await util.promisify(gServer.close.bind(gServer))();
|
||||
gServer = null;
|
||||
const [error] = await safe(shell.sudo([ SET_LDAP_ALLOWLIST_CMD ], {}));
|
||||
if (error) throw new BoxError(BoxError.IPTABLES_ERROR, `Error setting ldap allowlist: ${error.message}`);
|
||||
|
||||
if (!config.enabled) {
|
||||
await stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gServer) await start();
|
||||
}
|
||||
|
||||
async function setConfig(directoryServerConfig, auditSource) {
|
||||
assert.strictEqual(typeof directoryServerConfig, 'object');
|
||||
assert(auditSource && typeof auditSource === 'object');
|
||||
|
||||
if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode');
|
||||
|
||||
const oldConfig = await getConfig();
|
||||
|
||||
const config = {
|
||||
enabled: directoryServerConfig.enabled,
|
||||
secret: directoryServerConfig.secret,
|
||||
allowlist: directoryServerConfig.allowlist || ''
|
||||
};
|
||||
|
||||
await validateConfig(config);
|
||||
await settings.setJson(settings.DIRECTORY_SERVER_KEY, config);
|
||||
await applyConfig(config);
|
||||
|
||||
await eventlog.add(eventlog.ACTION_DIRECTORY_SERVER_CONFIGURE, auditSource, { fromEnabled: oldConfig.enabled, toEnabled: config.enabled });
|
||||
}
|
||||
|
||||
async function checkCertificate() {
|
||||
|
||||
Reference in New Issue
Block a user