diff --git a/defaults/config.js b/defaults/config.js index 98f4876c..742449f7 100644 --- a/defaults/config.js +++ b/defaults/config.js @@ -329,6 +329,23 @@ module.exports = { // @type object // @default {} // + // The authentication process works as follows: + // + // 1. Lounge connects to the LDAP server with its system credentials + // 2. It performs a LDAP search query to find the full DN associated to the + // user requesting to log in. + // 3. Lounge tries to connect a second time, but this time using the user's + // DN and password. Auth is validated iff this connection is successful. + // + // The search query takes a couple of parameters: + // - A base DN. Only children nodes of this DN will be likely to be returned + // - A search scope (see LDAP documentation) + // - The query itself, build as (&(=) ) + // where is the user name provided in the log in request, + // is provided by the config and is a filtering complement + // also given in the config, to filter for instance only for nodes of type + // inetOrgPerson, or whatever LDAP search allows. + // ldap: { // // Enable LDAP user authentication @@ -346,11 +363,35 @@ module.exports = { url: "ldaps://example.com", // - // LDAP base dn + // LDAP connection tls options (only used if scheme is ldaps://) + // + // @type object (see nodejs' tls.connect() options) + // @default {} + // + // Example: + // You can use this option in order to force the use of IPv6: + // { + // host: 'my::ip::v6' + // servername: 'ldaps://example.com' + // } + tlsOptions: {}, + + // + // LDAP searching bind DN + // This bind DN is used to query the server for the DN of the user. + // This is supposed to be a system user that has access in read only to + // the DNs of the people that are allowed to log in. // // @type string // - baseDN: "ou=accounts,dc=example,dc=com", + rootDN: "cn=thelounge,ou=system-users,dc=example,dc=com", + + // + // Password of the lounge LDAP system user + // + // @type string + // + rootPassword: "1234", // // LDAP primary key @@ -358,7 +399,30 @@ module.exports = { // @type string // @default "uid" // - primaryKey: "uid" + primaryKey: "uid", + + // + // LDAP filter + // + // @type string + // @default "uid" + // + filter: "(objectClass=inetOrgPerson)(memberOf=ou=accounts,dc=example,dc=com)", + + // + // LDAP search base (search only within this node) + // + // @type string + // + base: "dc=example,dc=com", + + // + // LDAP search scope + // + // @type string + // @default "sub" + // + scope: "sub" }, // Extra debugging diff --git a/src/server.js b/src/server.js index 636b5ab6..9fcd759a 100644 --- a/src/server.js +++ b/src/server.js @@ -283,26 +283,73 @@ function localAuth(client, user, password, callback) { } function ldapAuth(client, user, password, callback) { + if (!user) { + return callback(false); + } var userDN = user.replace(/([,\\/#+<>;"= ])/g, "\\$1"); - var bindDN = Helper.config.ldap.primaryKey + "=" + userDN + "," + Helper.config.ldap.baseDN; var ldapclient = ldap.createClient({ - url: Helper.config.ldap.url + url: Helper.config.ldap.url, + tlsOptions: Helper.config.ldap.tlsOptions }); + var base = Helper.config.ldap.base; + var searchOptions = { + scope: Helper.config.ldap.scope, + filter: '(&(' + Helper.config.ldap.primaryKey + '=' + userDN + ')' + Helper.config.ldap.filter + ')', + attributes: ['dn'] + }; + ldapclient.on("error", function(err) { log.error("Unable to connect to LDAP server", err); callback(!err); }); - ldapclient.bind(bindDN, password, function(err) { - if (!err && !client) { - if (!manager.addUser(user, null)) { - log.error("Unable to create new user", user); - } + ldapclient.bind(Helper.config.ldap.rootDN, + Helper.config.ldap.rootPassword, + function(err) { + if (err) { + log.error("Invalid LDAP root credentials"); + ldapclient.unbind(); + callback(false); + } else { + ldapclient.search(base, searchOptions, function(err, res) { + if (err) { + log.warning("User not found: ", userDN); + ldapclient.unbind(); + callback(false); + } else { + var found = false; + res.on('searchEntry', function(entry) { + found = true; + var bindDN = entry.objectName; + log.info("Auth against LDAP ", Helper.config.ldap.url, " with bindDN ", bindDN); + ldapclient.unbind() + var ldapclient2 = ldap.createClient({ + url: Helper.config.ldap.url, + tlsOptions: Helper.config.ldap.tlsOptions + }); + ldapclient2.bind(bindDN, password, function(err) { + if (!err && !client) { + if (!manager.addUser(user, null)) { + log.error("Unable to create new user", user); + } + } + ldapclient2.unbind(); + callback(!err); + }); + }); + res.on('error', function(resErr) { + callback(false); + }); + res.on('end', function(result) { + if (!found) { + callback(false); + } + }); + } + }); } - ldapclient.unbind(); - callback(!err); }); }