2017-08-29 12:05:06 -04:00
|
|
|
"use strict";
|
|
|
|
|
2018-06-15 16:31:06 -04:00
|
|
|
const log = require("../../log");
|
2017-08-29 12:05:06 -04:00
|
|
|
const Helper = require("../../helper");
|
2019-09-04 06:49:48 -04:00
|
|
|
const ldap = require("ldapjs");
|
2020-04-22 09:33:09 -04:00
|
|
|
const colors = require("chalk");
|
2017-08-29 12:05:06 -04:00
|
|
|
|
2017-08-30 05:49:21 -04:00
|
|
|
function ldapAuthCommon(user, bindDN, password, callback) {
|
2017-08-29 13:11:06 -04:00
|
|
|
const config = Helper.config;
|
|
|
|
|
|
|
|
const ldapclient = ldap.createClient({
|
|
|
|
url: config.ldap.url,
|
2017-11-15 01:35:15 -05:00
|
|
|
tlsOptions: config.ldap.tlsOptions,
|
2017-08-29 13:11:06 -04:00
|
|
|
});
|
|
|
|
|
2020-03-21 16:55:36 -04:00
|
|
|
ldapclient.on("error", function (err) {
|
2017-09-01 05:29:52 -04:00
|
|
|
log.error(`Unable to connect to LDAP server: ${err}`);
|
2018-12-12 15:33:30 -05:00
|
|
|
callback(false);
|
2017-08-29 13:11:06 -04:00
|
|
|
});
|
|
|
|
|
2020-03-21 16:55:36 -04:00
|
|
|
ldapclient.bind(bindDN, password, function (err) {
|
2017-08-29 13:11:06 -04:00
|
|
|
ldapclient.unbind();
|
2018-12-12 15:33:30 -05:00
|
|
|
|
|
|
|
if (err) {
|
|
|
|
log.error(`LDAP bind failed: ${err}`);
|
|
|
|
callback(false);
|
|
|
|
} else {
|
|
|
|
callback(true);
|
|
|
|
}
|
2017-08-29 13:11:06 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-08-30 05:49:21 -04:00
|
|
|
function simpleLdapAuth(user, password, callback) {
|
2017-11-21 19:19:24 -05:00
|
|
|
if (!user || !password) {
|
2017-08-29 12:05:06 -04:00
|
|
|
return callback(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
const config = Helper.config;
|
|
|
|
|
|
|
|
const userDN = user.replace(/([,\\/#+<>;"= ])/g, "\\$1");
|
2017-09-01 05:29:52 -04:00
|
|
|
const bindDN = `${config.ldap.primaryKey}=${userDN},${config.ldap.baseDN}`;
|
2017-08-29 12:05:06 -04:00
|
|
|
|
2017-09-01 05:29:52 -04:00
|
|
|
log.info(`Auth against LDAP ${config.ldap.url} with provided bindDN ${bindDN}`);
|
2017-08-29 12:05:06 -04:00
|
|
|
|
2017-08-30 05:49:21 -04:00
|
|
|
ldapAuthCommon(user, bindDN, password, callback);
|
2017-08-29 13:11:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* LDAP auth using initial DN search (see config comment for ldap.searchDN)
|
|
|
|
*/
|
2017-08-30 05:49:21 -04:00
|
|
|
function advancedLdapAuth(user, password, callback) {
|
2017-11-21 19:19:24 -05:00
|
|
|
if (!user || !password) {
|
2017-08-29 13:11:06 -04:00
|
|
|
return callback(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
const config = Helper.config;
|
|
|
|
const userDN = user.replace(/([,\\/#+<>;"= ])/g, "\\$1");
|
|
|
|
|
|
|
|
const ldapclient = ldap.createClient({
|
|
|
|
url: config.ldap.url,
|
2017-11-15 01:35:15 -05:00
|
|
|
tlsOptions: config.ldap.tlsOptions,
|
2017-08-29 13:11:06 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
const base = config.ldap.searchDN.base;
|
|
|
|
const searchOptions = {
|
|
|
|
scope: config.ldap.searchDN.scope,
|
2017-09-01 05:29:52 -04:00
|
|
|
filter: `(&(${config.ldap.primaryKey}=${userDN})${config.ldap.searchDN.filter})`,
|
2017-11-15 01:35:15 -05:00
|
|
|
attributes: ["dn"],
|
2017-08-29 13:11:06 -04:00
|
|
|
};
|
|
|
|
|
2020-03-21 16:55:36 -04:00
|
|
|
ldapclient.on("error", function (err) {
|
2017-09-01 05:29:52 -04:00
|
|
|
log.error(`Unable to connect to LDAP server: ${err}`);
|
2018-12-12 15:33:30 -05:00
|
|
|
callback(false);
|
2017-08-29 13:11:06 -04:00
|
|
|
});
|
|
|
|
|
2020-03-21 16:55:36 -04:00
|
|
|
ldapclient.bind(config.ldap.searchDN.rootDN, config.ldap.searchDN.rootPassword, function (err) {
|
2017-08-29 13:11:06 -04:00
|
|
|
if (err) {
|
|
|
|
log.error("Invalid LDAP root credentials");
|
|
|
|
ldapclient.unbind();
|
|
|
|
callback(false);
|
2020-04-22 09:33:09 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ldapclient.search(base, searchOptions, function (err2, res) {
|
|
|
|
if (err2) {
|
|
|
|
log.warn(`LDAP User not found: ${userDN}`);
|
|
|
|
ldapclient.unbind();
|
|
|
|
callback(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let found = false;
|
|
|
|
|
|
|
|
res.on("searchEntry", function (entry) {
|
|
|
|
found = true;
|
|
|
|
const bindDN = entry.objectName;
|
|
|
|
log.info(`Auth against LDAP ${config.ldap.url} with found bindDN ${bindDN}`);
|
|
|
|
ldapclient.unbind();
|
|
|
|
|
|
|
|
ldapAuthCommon(user, bindDN, password, callback);
|
|
|
|
});
|
|
|
|
|
|
|
|
res.on("error", function (err3) {
|
|
|
|
log.error(`LDAP error: ${err3}`);
|
|
|
|
callback(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
res.on("end", function (result) {
|
|
|
|
ldapclient.unbind();
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
log.warn(`LDAP Search did not find anything for: ${userDN} (${result.status})`);
|
2017-08-29 13:11:06 -04:00
|
|
|
callback(false);
|
|
|
|
}
|
|
|
|
});
|
2020-04-22 09:33:09 -04:00
|
|
|
});
|
2017-08-29 13:11:06 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function ldapAuth(manager, client, user, password, callback) {
|
2017-08-30 05:49:21 -04:00
|
|
|
// TODO: Enable the use of starttls() as an alternative to ldaps
|
|
|
|
|
|
|
|
// TODO: move this out of here and get rid of `manager` and `client` in
|
|
|
|
// auth plugin API
|
|
|
|
function callbackWrapper(valid) {
|
|
|
|
if (valid && !client) {
|
2018-02-23 20:07:08 -05:00
|
|
|
manager.addUser(user, null, true);
|
2017-08-30 05:49:21 -04:00
|
|
|
}
|
2018-02-20 02:28:04 -05:00
|
|
|
|
2017-08-30 05:49:21 -04:00
|
|
|
callback(valid);
|
|
|
|
}
|
|
|
|
|
|
|
|
let auth;
|
2018-02-20 02:28:04 -05:00
|
|
|
|
2017-08-29 13:11:06 -04:00
|
|
|
if ("baseDN" in Helper.config.ldap) {
|
|
|
|
auth = simpleLdapAuth;
|
|
|
|
} else {
|
|
|
|
auth = advancedLdapAuth;
|
|
|
|
}
|
2018-02-20 02:28:04 -05:00
|
|
|
|
2017-08-30 05:49:21 -04:00
|
|
|
return auth(user, password, callbackWrapper);
|
2017-08-29 13:11:06 -04:00
|
|
|
}
|
|
|
|
|
2020-04-22 09:33:09 -04:00
|
|
|
/**
|
|
|
|
* Use the LDAP filter from config to check that users still exist before loading them
|
|
|
|
* via the supplied callback function.
|
|
|
|
*/
|
|
|
|
|
|
|
|
function advancedLdapLoadUsers(users, callbackLoadUser) {
|
|
|
|
const config = Helper.config;
|
|
|
|
|
|
|
|
const ldapclient = ldap.createClient({
|
|
|
|
url: config.ldap.url,
|
|
|
|
tlsOptions: config.ldap.tlsOptions,
|
|
|
|
});
|
|
|
|
|
|
|
|
const base = config.ldap.searchDN.base;
|
|
|
|
|
|
|
|
ldapclient.on("error", function (err) {
|
|
|
|
log.error(`Unable to connect to LDAP server: ${err}`);
|
|
|
|
});
|
|
|
|
|
|
|
|
ldapclient.bind(config.ldap.searchDN.rootDN, config.ldap.searchDN.rootPassword, function (err) {
|
|
|
|
if (err) {
|
|
|
|
log.error("Invalid LDAP root credentials");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const remainingUsers = new Set(users);
|
|
|
|
|
|
|
|
const searchOptions = {
|
|
|
|
scope: config.ldap.searchDN.scope,
|
|
|
|
filter: `${config.ldap.searchDN.filter}`,
|
|
|
|
attributes: [config.ldap.primaryKey],
|
|
|
|
paged: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
ldapclient.search(base, searchOptions, function (err2, res) {
|
|
|
|
if (err2) {
|
|
|
|
log.error(`LDAP search error: ${err2}`);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
res.on("searchEntry", function (entry) {
|
|
|
|
const user = entry.attributes[0]._vals[0].toString();
|
|
|
|
|
|
|
|
if (remainingUsers.has(user)) {
|
|
|
|
remainingUsers.delete(user);
|
|
|
|
callbackLoadUser(user);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
res.on("error", function (err3) {
|
|
|
|
log.error(`LDAP error: ${err3}`);
|
|
|
|
});
|
|
|
|
|
|
|
|
res.on("end", function () {
|
|
|
|
remainingUsers.forEach((user) => {
|
|
|
|
log.warn(
|
|
|
|
`No account info in LDAP for ${colors.bold(
|
|
|
|
user
|
|
|
|
)} but user config file exists`
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
ldapclient.unbind();
|
|
|
|
});
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function ldapLoadUsers(users, callbackLoadUser) {
|
|
|
|
if ("baseDN" in Helper.config.ldap) {
|
|
|
|
// simple LDAP case can't test for user existence without access to the
|
|
|
|
// user's unhashed password, so indicate need to fallback to default
|
|
|
|
// loadUser behaviour by returning false
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return advancedLdapLoadUsers(users, callbackLoadUser);
|
|
|
|
}
|
|
|
|
|
2017-08-29 13:11:06 -04:00
|
|
|
function isLdapEnabled() {
|
|
|
|
return !Helper.config.public && Helper.config.ldap.enable;
|
2017-08-29 12:05:06 -04:00
|
|
|
}
|
|
|
|
|
2017-08-29 13:11:06 -04:00
|
|
|
module.exports = {
|
2020-04-22 09:33:35 -04:00
|
|
|
moduleName: "ldap",
|
2017-08-29 13:11:06 -04:00
|
|
|
auth: ldapAuth,
|
2017-11-15 01:35:15 -05:00
|
|
|
isEnabled: isLdapEnabled,
|
2020-04-22 09:33:09 -04:00
|
|
|
loadUsers: ldapLoadUsers,
|
2017-08-29 13:11:06 -04:00
|
|
|
};
|