settings: move externaldap setting
This commit is contained in:
@@ -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' });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user