Merge pull request #3871 from ebardie/ebardie/dont_load_extinct_users
Filter user loading at startup for "advanced" LDAP
This commit is contained in:
commit
4ac25d4bc5
@ -6,6 +6,7 @@ const colors = require("chalk");
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const Auth = require("./plugins/auth");
|
||||
const Client = require("./client");
|
||||
const Helper = require("./helper");
|
||||
const WebPush = require("./plugins/webpush");
|
||||
@ -45,7 +46,15 @@ ClientManager.prototype.loadUsers = function () {
|
||||
);
|
||||
}
|
||||
|
||||
// This callback is used by Auth plugins to load users they deem acceptable
|
||||
const callbackLoadUser = (user) => {
|
||||
this.loadUser(user);
|
||||
};
|
||||
|
||||
if (!Auth.loadUsers(users, callbackLoadUser)) {
|
||||
// Fallback to loading all users
|
||||
users.forEach((name) => this.loadUser(name));
|
||||
}
|
||||
};
|
||||
|
||||
ClientManager.prototype.autoloadUsers = function () {
|
||||
|
50
src/plugins/auth.js
Normal file
50
src/plugins/auth.js
Normal file
@ -0,0 +1,50 @@
|
||||
"use strict";
|
||||
|
||||
const log = require("../log");
|
||||
const colors = require("chalk");
|
||||
|
||||
// The order defines priority: the first available plugin is used.
|
||||
// Always keep 'local' auth plugin at the end of the list; it should always be enabled.
|
||||
const plugins = [require("./auth/ldap"), require("./auth/local")];
|
||||
|
||||
function unimplemented(funcName) {
|
||||
log.debug(
|
||||
`Auth module ${colors.bold(
|
||||
module.exports.moduleName
|
||||
)} doesn't implement function ${colors.bold(funcName)}`
|
||||
);
|
||||
}
|
||||
|
||||
// Default API implementations
|
||||
module.exports = {
|
||||
moduleName: "<module with no name>",
|
||||
|
||||
// Must override: implements authentication mechanism
|
||||
auth: () => unimplemented("auth"),
|
||||
|
||||
// Optional to override: implements filter for loading users at start up
|
||||
// This allows an auth plugin to check if a user is still acceptable, if the plugin
|
||||
// can do so without access to the user's unhashed password.
|
||||
// Returning 'false' triggers fallback to default behaviour of loading all users
|
||||
loadUsers: () => false,
|
||||
};
|
||||
|
||||
// local auth should always be enabled, but check here to verify
|
||||
let somethingEnabled = false;
|
||||
|
||||
// Override default API stubs with exports from first enabled plugin found
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.isEnabled()) {
|
||||
somethingEnabled = true;
|
||||
|
||||
for (const name in plugin) {
|
||||
module.exports[name] = plugin[name];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!somethingEnabled) {
|
||||
log.error("None of the auth plugins is enabled");
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
const log = require("../../log");
|
||||
const Helper = require("../../helper");
|
||||
const ldap = require("ldapjs");
|
||||
const colors = require("chalk");
|
||||
|
||||
function ldapAuthCommon(user, bindDN, password, callback) {
|
||||
const config = Helper.config;
|
||||
@ -77,41 +78,42 @@ function advancedLdapAuth(user, password, callback) {
|
||||
log.error("Invalid LDAP root credentials");
|
||||
ldapclient.unbind();
|
||||
callback(false);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
ldapclient.search(base, searchOptions, function (err2, res) {
|
||||
if (err2) {
|
||||
log.warn(`LDAP User not found: ${userDN}`);
|
||||
ldapclient.unbind();
|
||||
callback(false);
|
||||
} else {
|
||||
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}`
|
||||
);
|
||||
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})`
|
||||
);
|
||||
log.warn(`LDAP Search did not find anything for: ${userDN} (${result.status})`);
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -139,11 +141,94 @@ function ldapAuth(manager, client, user, password, callback) {
|
||||
return auth(user, password, callbackWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
function isLdapEnabled() {
|
||||
return !Helper.config.public && Helper.config.ldap.enable;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
moduleName: "ldap",
|
||||
auth: ldapAuth,
|
||||
isEnabled: isLdapEnabled,
|
||||
loadUsers: ldapLoadUsers,
|
||||
};
|
||||
|
@ -46,6 +46,7 @@ function localAuth(manager, client, user, password, callback) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
moduleName: "local",
|
||||
auth: localAuth,
|
||||
isEnabled: () => true,
|
||||
};
|
||||
|
@ -17,16 +17,13 @@ const net = require("net");
|
||||
const Identification = require("./identification");
|
||||
const changelog = require("./plugins/changelog");
|
||||
const inputs = require("./plugins/inputs");
|
||||
const Auth = require("./plugins/auth");
|
||||
|
||||
const themes = require("./plugins/packages/themes");
|
||||
themes.loadLocalThemes();
|
||||
|
||||
const packages = require("./plugins/packages/index");
|
||||
|
||||
// The order defined the priority: the first available plugin is used
|
||||
// ALways keep local auth in the end, which should always be enabled.
|
||||
const authPlugins = [require("./plugins/auth/ldap"), require("./plugins/auth/local")];
|
||||
|
||||
// A random number that will force clients to reload the page if it differs
|
||||
const serverHash = Math.floor(Date.now() * Math.random());
|
||||
|
||||
@ -851,18 +848,7 @@ function performAuthentication(data) {
|
||||
}
|
||||
|
||||
// Perform password checking
|
||||
let auth = () => {
|
||||
log.error("None of the auth plugins is enabled");
|
||||
};
|
||||
|
||||
for (let i = 0; i < authPlugins.length; ++i) {
|
||||
if (authPlugins[i].isEnabled()) {
|
||||
auth = authPlugins[i].auth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auth(manager, client, data.user, data.password, authCallback);
|
||||
Auth.auth(manager, client, data.user, data.password, authCallback);
|
||||
}
|
||||
|
||||
function reverseDnsLookup(ip, callback) {
|
||||
|
Loading…
Reference in New Issue
Block a user