externalldap: async'ify

and make the tests work again
This commit is contained in:
Girish Ramakrishnan
2021-09-01 13:09:49 -07:00
parent 167eae5b81
commit 1856caf972
5 changed files with 479 additions and 773 deletions
+181 -381
View File
@@ -7,41 +7,20 @@
const async = require('async'),
BoxError = require('../boxerror.js'),
database = require('../database.js'),
constants = require('../constants.js'),
common = require('./common.js'),
expect = require('expect.js'),
externalldap = require('../externalldap.js'),
externalLdap = require('../externalldap.js'),
groups = require('../groups.js'),
domains = require('../domains.js'),
ldap = require('ldapjs'),
mailer = require('../mailer.js'),
safe = require('safetydance'),
server = require('../server.js'),
settings = require('../settings.js'),
superagent = require('superagent'),
users = require('../users.js'),
util = require('util'),
_ = require('underscore');
var USERNAME = 'noBody';
var EMAIL = 'else@no.body';
var PASSWORD = 'sTrOnG#$34134';
var DISPLAY_NAME = 'Nobody cares';
var AUDIT_SOURCE = { ip: '1.2.3.4', userId: 'someuserid' };
let gLdapServer;
const SERVER_URL = `http://localhost:${constants.PORT}`;
const DOMAIN_0 = {
domain: 'example.com',
zoneName: 'example.com',
provider: 'manual',
config: {},
fallbackCertificate: null,
tlsConfig: { provider: 'fallback' },
wellKnown: null
};
const LDAP_SHARED_PASSWORD = 'validpassword';
const LDAP_PORT = 4321;
const LDAP_BASE_DN = 'ou=Users,dc=cloudron,dc=io';
@@ -59,23 +38,6 @@ const LDAP_CONFIG = {
autoCreate: false
};
function cleanupUsers(done) {
mailer._mailQueue = [];
async.series([
database._clear,
], done);
}
function createOwner(done) {
users.createOwner(USERNAME, PASSWORD, EMAIL, DISPLAY_NAME, AUDIT_SOURCE, function (error, result) {
expect(error).to.not.be.ok();
expect(result).to.be.ok();
done();
});
}
// helper function to deal with pagination taken from ldap.js
function finalSend(results, req, res, next) {
var min = 0;
@@ -212,365 +174,243 @@ function stopLdapServer(callback) {
callback();
}
function setup(done) {
mailer._mailQueue = [];
async.series([
startLdapServer,
server.start,
database._clear,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
cleanupUsers,
createOwner,
settings.setDashboardLocation.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain)
], done);
}
function cleanup(done) {
mailer._mailQueue = [];
async.series([
database._clear,
server.stop,
stopLdapServer
], done);
}
function enable(config, callback) {
if (typeof config === 'function') {
callback = config;
config = LDAP_CONFIG;
}
settings.setExternalLdapConfig(config, callback);
}
function disable(callback) {
const config = {
provider: 'noop'
};
settings.setExternalLdapConfig(config, callback);
}
describe('External LDAP', function () {
before(setup);
after(cleanup);
const { setup, cleanup, admin, serverUrl, auditSource } = common;
before(function (done) {
async.series([
startLdapServer,
setup
], done);
});
after(function (done) {
async.series([
stopLdapServer,
cleanup
], done);
});
describe('settings', function () {
it('enabling fails with missing url', function (done) {
it('enabling fails with missing url', async function () {
let conf = _.extend({}, LDAP_CONFIG);
delete conf.url;
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling fails with empty url', function (done) {
it('enabling fails with empty url', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.url = '';
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling fails with missing baseDn', function (done) {
it('enabling fails with missing baseDn', async function () {
let conf = _.extend({}, LDAP_CONFIG);
delete conf.baseDn;
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling fails with empty baseDn', function (done) {
it('enabling fails with empty baseDn', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.baseDn = '';
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling fails with missing filter', function (done) {
it('enabling fails with missing filter', async function () {
let conf = _.extend({}, LDAP_CONFIG);
delete conf.filter;
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling fails with empty filter', function (done) {
it('enabling fails with empty filter', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.filter = '';
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling succeeds', function (done) {
enable(function (error) {
expect(error).to.equal(null);
done();
});
it('enabling succeeds', async function () {
await settings.setExternalLdapConfig(LDAP_CONFIG);
});
it('disabling succeeds', function (done) {
disable(function (error) {
expect(error).to.equal(null);
done();
});
it('disabling succeeds', async function () {
await settings.setExternalLdapConfig({ provider: 'noop' });
});
// now test with groups
it('enabling with groups fails with missing groupBaseDn', function (done) {
it('enabling with groups fails with missing groupBaseDn', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.syncGroups = true;
delete conf.groupBaseDn;
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling with groups fails with empty groupBaseDn', function (done) {
it('enabling with groups fails with empty groupBaseDn', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.syncGroups = true;
conf.groupBaseDn = '';
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling with groups fails with missing groupFilter', function (done) {
it('enabling with groups fails with missing groupFilter', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.syncGroups = true;
delete conf.groupFilter;
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling with groups fails with empty groupFilter', function (done) {
it('enabling with groups fails with empty groupFilter', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.syncGroups = true;
conf.groupFilter = '';
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling with groups fails with missing groupnameField', function (done) {
it('enabling with groups fails with missing groupnameField', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.syncGroups = true;
delete conf.groupnameField;
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling with groups fails with empty groupnameField', function (done) {
it('enabling with groups fails with empty groupnameField', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.syncGroups = true;
conf.groupnameField = '';
enable(conf, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_FIELD);
done();
});
const [error] = await safe(settings.setExternalLdapConfig(conf));
expect(error.reason).to.equal(BoxError.BAD_FIELD);
});
it('enabling with groups succeeds', function (done) {
it('enabling with groups succeeds', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.syncGroups = true;
enable(function (error) {
expect(error).to.equal(null);
done();
});
});
it('disabling succeeds', function (done) {
disable(function (error) {
expect(error).to.equal(null);
done();
});
await settings.setExternalLdapConfig(conf);
});
});
describe('sync', function () {
it('fails if disabled', function (done) {
externalldap.sync(function progress() {}, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.equal(BoxError.BAD_STATE);
done();
});
it('disable sync', async function () {
await settings.setExternalLdapConfig({ provider: 'noop' });
});
it('enable', enable);
it('fails if disabled', async function () {
const [error] = await safe(externalLdap.sync(function progress() {}));
expect(error.reason).to.equal(BoxError.BAD_STATE);
});
it('succeeds for new users', function (done) {
it('enable', async function () {
await settings.setExternalLdapConfig(LDAP_CONFIG);
});
it('succeeds for new users', async function () {
gLdapUsers.push({
username: 'firstuser',
displayName: 'First User',
email: 'first@user.com'
});
externalldap.sync(function progress() {}, function (error) {
expect(error).to.equal(null);
users.list(function (error, result) {
expect(error).to.equal(null);
expect(result.length).to.equal(2);
expect(result.find(function (u) {
return u.username === 'firstuser' && u.email === 'first@user.com' && u.displayName === 'First User';
})).to.be.ok();
done();
});
});
await externalLdap.sync(function progress() {});
const result = await users.list();
expect(result.find(function (u) {
return u.username === 'firstuser' && u.email === 'first@user.com' && u.displayName === 'First User';
})).to.be.ok();
});
it('succeeds for updated users', function (done) {
it('succeeds for updated users', async function () {
gLdapUsers[0].displayName = 'User First';
gLdapUsers[0].email = 'first@changed.com';
externalldap.sync(function progress() {}, function (error) {
expect(error).to.equal(null);
users.list(function (error, result) {
expect(error).to.equal(null);
expect(result.length).to.equal(2);
expect(result.find(function (u) {
return u.username === 'firstuser' && u.email === 'first@changed.com' && u.displayName === 'User First';
})).to.be.ok();
done();
});
});
await externalLdap.sync(function progress() {});
const result = await users.list();
expect(result.find(function (u) {
return u.username === 'firstuser' && u.email === 'first@changed.com' && u.displayName === 'User First';
})).to.be.ok();
});
it('ignores already existing users with same username', function (done) {
it('ignores already existing users with same username', async function () {
gLdapUsers.push({
username: USERNAME,
username: admin.username,
displayName: 'Something Else',
email: 'foobar@bar.com'
});
externalldap.sync(function progress() {}, function (error) {
expect(error).to.equal(null);
users.list(function (error, result) {
expect(error).to.equal(null);
expect(result.length).to.equal(2);
expect(result.find(function (u) {
return u.email === 'foobar@bar.com' || u.displayName === 'Something Else';
})).to.not.be.ok();
done();
});
});
await externalLdap.sync(function progress() {});
const result = await users.list();
expect(result.find(function (u) {
return u.email === 'foobar@bar.com' || u.displayName === 'Something Else';
})).to.not.be.ok();
});
it('does not sync group if group sync is disabled', function (done) {
it('does not sync group if group sync is disabled', async function () {
gLdapGroups.push({
groupname: 'extGroup1'
});
externalldap.sync(function progress() {}, async function (error) {
expect(error).to.equal(null);
const result = await groups.list();
expect(result.length).to.equal(0);
done();
});
await externalLdap.sync(function progress() {});
const result = await groups.list();
expect(result.length).to.equal(0);
});
it('enable with groupSync', function (done) {
disable(function (error) {
expect(error).to.equal(null);
let conf = _.extend({}, LDAP_CONFIG);
conf.syncGroups = true;
enable(conf, done);
});
it('enable with groupSync', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.syncGroups = true;
await settings.setExternalLdapConfig(conf);
});
it('succeeds with groups enabled', function (done) {
it('succeeds with groups enabled', async function () {
gLdapGroups = [];
externalldap.sync(function progress() {}, async function (error) {
expect(error).to.equal(null);
const result = await groups.list();
expect(result.length).to.equal(0);
done();
});
await externalLdap.sync(function progress() {});
const result = await groups.list();
expect(result.length).to.equal(0);
});
it('succeeds with groups enabled and new group', function (done) {
it('succeeds with groups enabled and new group', async function () {
gLdapGroups.push({
groupname: 'extGroup1'
});
externalldap.sync(function progress() {}, async function (error) {
expect(error).to.equal(null);
const result = await groups.list();
expect(result.length).to.equal(1);
done();
});
await externalLdap.sync(function progress() {});
const result = await groups.list();
expect(result.find(function (g) {
return g.name === 'extgroup1';
})).to.be.ok();
});
it('succeeds with groups enabled and second new group', function (done) {
it('succeeds with groups enabled and second new group', async function () {
gLdapGroups.push({
groupname: 'extGroup2'
});
externalldap.sync(function progress() {}, async function (error) {
expect(error).to.equal(null);
const result = await groups.list();
expect(result.length).to.equal(2);
done();
});
await externalLdap.sync(function progress() {});
const result = await groups.list();
expect(result.length).to.be(2);
expect(result.find(function (g) {
return g.name === 'extgroup2';
})).to.be.ok();
});
it('does not create already existing group', async function () {
@@ -578,184 +418,144 @@ describe('External LDAP', function () {
groupname: 'INTERNALgroup' // also tests lowercasing
});
const externalldapSync = util.promisify(externalldap.sync);
await groups.add({ name: 'internalgroup' });
await externalldapSync(function progress() {});
await externalLdap.sync(function progress() {});
const result = await groups.list();
expect(result.length).to.equal(3);
});
it('adds users of groups', function (done) {
it('adds users of groups', async function () {
gLdapGroups.push({
groupname: 'nonEmptyGroup',
member: gLdapUsers.slice(-2).map(function (u) { return `cn=${u.username},${LDAP_CONFIG.baseDn}`; })
});
externalldap.sync(function progress() {}, async function (error) {
expect(error).to.equal(null);
await externalLdap.sync(function progress() {});
const result = await groups.getByName('nonemptygroup');
expect(result).to.be.ok();
const result = await groups.getByName('nonemptygroup');
expect(result).to.be.ok();
const result2 = await groups.getMembers(result.id);
expect(result2.length).to.equal(2);
done();
});
const result2 = await groups.getMembers(result.id);
expect(result2.length).to.equal(2);
});
it('adds new users of groups', function (done) {
it('adds new users of groups', async function () {
gLdapGroups.push({
groupname: 'nonEmptyGroup',
member: gLdapUsers.map(function (u) { return `cn=${u.username},${LDAP_CONFIG.baseDn}`; }) // has 2 entries
});
externalldap.sync(function progress() {}, async function (error) {
expect(error).to.equal(null);
await externalLdap.sync(function progress() {});
const result = await groups.getByName('nonemptygroup');
expect(result).to.be.ok();
const result = await groups.getByName('nonemptygroup');
expect(result).to.be.ok();
const result2 = await groups.getMembers(result.id);
expect(result2.length).to.equal(2);
done();
});
const result2 = await groups.getMembers(result.id);
expect(result2.length).to.equal(2);
});
it('succeeds with only one group member (string instead of array)', function (done) {
it('succeeds with only one group member (string instead of array)', async function () {
gLdapGroups.push({
groupname: 'onemembergroup',
member: `cn=${gLdapUsers[0].username},${LDAP_CONFIG.baseDn}`
});
externalldap.sync(function progress() {}, async function (error) {
expect(error).to.equal(null);
await externalLdap.sync(function progress() {});
const result = await groups.getByName('onemembergroup');
const result2 = await groups.getMembers(result.id);
expect(result2.length).to.equal(1);
const result = await groups.getByName('onemembergroup');
const result2 = await groups.getMembers(result.id);
expect(result2.length).to.equal(1);
users.get(result2[0], function (error, result) {
expect(error).to.equal(null);
expect(result.username).to.equal(gLdapUsers[0].username);
done();
});
});
const u = await users.get(result2[0]);
expect(u.username).to.equal(gLdapUsers[0].username);
});
it('disable', disable);
});
describe('user auto creation', function () {
it('fails if external ldap is disabled', function (done) {
settings.setExternalLdapConfig({ provider: 'noop' }, function (error) {
expect(error).to.equal(null);
done();
});
before(server.start);
after(server.stop);
it('enable', async function () {
await settings.setExternalLdapConfig(LDAP_CONFIG);
});
it('enable', enable);
it('fails if auto create is disabled', function (done) {
it('fails if auto create is disabled', async function () {
gLdapUsers.push({
username: 'autologinuser0',
displayName: 'Auto Login0',
email: 'auto0@login.com'
});
superagent.post(SERVER_URL + '/api/v1/cloudron/login')
const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`)
.send({ username: 'autologinuser0', password: LDAP_SHARED_PASSWORD })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
.ok(() => true);
users.list(function (error, result) {
expect(error).to.equal(null);
expect(result.length).to.equal(2);
expect(result.find(function (u) {
return u.username === 'autologinuser0';
})).to.not.be.ok();
expect(response.status).to.equal(401);
done();
});
});
const result = await users.list();
expect(result.find(function (u) {
return u.username === 'autologinuser0';
})).to.not.be.ok();
});
it('enable auto create', function (done) {
it('enable auto create', async function () {
let conf = _.extend({}, LDAP_CONFIG);
conf.autoCreate = true;
enable(conf, done);
await settings.setExternalLdapConfig(conf);
});
it('fails for unknown user', function (done) {
superagent.post(SERVER_URL + '/api/v1/cloudron/login')
it('fails for unknown user', async function () {
const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`)
.send({ username: 'doesnotexist', password: LDAP_SHARED_PASSWORD })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
.ok(() => true);
users.list(function (error, result) {
expect(error).to.equal(null);
expect(result.length).to.equal(2);
expect(result.find(function (u) {
return u.username === 'doesnotexist';
})).to.not.be.ok();
expect(response.status).to.equal(401);
done();
});
});
const result = await users.list();
expect(result.find(function (u) {
return u.username === 'doesnotexist';
})).to.not.be.ok();
});
it('succeeds for known user with wrong password', function (done) {
it('succeeds for known user with wrong password', async function () {
gLdapUsers.push({
username: 'autologinuser1',
displayName: 'Auto Login1',
email: 'auto1@login.com'
});
superagent.post(SERVER_URL + '/api/v1/cloudron/login')
const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`)
.send({ username: 'autologinuser1', password: 'wrongpassword' })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
.ok(() => true);
users.list(function (error, result) {
expect(error).to.equal(null);
expect(result.length).to.equal(3);
expect(result.find(function (u) {
return u.username === 'autologinuser1';
})).to.be.ok();
done();
});
});
expect(response.status).to.equal(401);
const result = await users.list();
expect(result.find(function (u) {
return u.username === 'autologinuser1';
})).to.be.ok();
});
it('succeeds for known user with correct password', function (done) {
gLdapUsers.push({
it('succeeds for known user with correct password', async function () {
const newUser = {
username: 'autologinuser2',
displayName: 'Auto Login2',
email: 'auto2@login.com'
});
email: 'auto2@login.com',
password: LDAP_SHARED_PASSWORD
};
superagent.post(SERVER_URL + '/api/v1/cloudron/login')
gLdapUsers.push(newUser);
await users.add(newUser.email, newUser, auditSource);
const response = await superagent.post(`${serverUrl}/api/v1/cloudron/login`)
.send({ username: 'autologinuser2', password: LDAP_SHARED_PASSWORD })
.end(function (error, result) {
expect(result.statusCode).to.equal(200);
.ok(() => true);
users.list(function (error, result) {
expect(error).to.equal(null);
expect(result.length).to.equal(4);
expect(result.find(function (u) {
return u.username === 'autologinuser2';
})).to.be.ok();
expect(response.status).to.equal(200);
done();
});
});
const result = await users.list();
expect(result.find(function (u) {
return u.username === 'autologinuser2';
})).to.be.ok();
});
it('disable', disable);
});
});