Merge pull request #18 from clems4ever/ldap-user-filter

Add an LDAP user search filter in the configuration filte to specify …
This commit is contained in:
Clément Michaud 2017-03-16 01:31:50 +01:00 committed by GitHub
commit ddf3fb7cbb
12 changed files with 138 additions and 26 deletions

View File

@ -3,9 +3,11 @@
logs_level: info logs_level: info
# Configuration of LDAP # Configuration of LDAP
# Example: for user john, the DN will be cn=john,ou=users,dc=example,dc=com
ldap: ldap:
url: ldap://ldap url: ldap://ldap
base_dn: ou=users,dc=example,dc=com user_search_base: ou=users,dc=example,dc=com
user_search_filter: cn
user: cn=admin,dc=example,dc=com user: cn=admin,dc=example,dc=com
password: password password: password

View File

@ -29,3 +29,18 @@ uid: user
uidnumber: 1000 uidnumber: 1000
userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g= userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
dn: uid=useruid,ou=users,dc=example,dc=com
cn: useruid
gidnumber: 500
givenname: user
homedirectory: /home/user1
loginshell: /bin/sh
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: top
mail: useruid@example.com
sn: User
uid: useruid
uidnumber: 1001
userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=

View File

@ -25,7 +25,8 @@ var yaml_config = YAML.load(config_path);
var config = { var config = {
port: process.env.PORT || 8080, port: process.env.PORT || 8080,
ldap_url: yaml_config.ldap.url || 'ldap://127.0.0.1:389', ldap_url: yaml_config.ldap.url || 'ldap://127.0.0.1:389',
ldap_users_dn: yaml_config.ldap.base_dn, ldap_user_search_base: yaml_config.ldap.user_search_base,
ldap_user_search_filter: yaml_config.ldap.user_search_filter,
ldap_user: yaml_config.ldap.user, ldap_user: yaml_config.ldap.user,
ldap_password: yaml_config.ldap.password, ldap_password: yaml_config.ldap.password,
session_domain: yaml_config.session.domain, session_domain: yaml_config.session.domain,

View File

@ -1,6 +1,6 @@
module.exports = { module.exports = {
validate: validateCredentials, validate: validate_credentials,
get_email: retrieve_email, get_email: retrieve_email,
update_password: update_password update_password: update_password
} }
@ -10,8 +10,12 @@ var Promise = require('bluebird');
var exceptions = require('./exceptions'); var exceptions = require('./exceptions');
var Dovehash = require('dovehash'); var Dovehash = require('dovehash');
function validateCredentials(ldap_client, username, password, users_dn) { function validate_credentials(ldap_client, username, password, user_base, user_filter) {
var userDN = util.format("cn=%s,%s", username, users_dn); // if not provided, default to cn
if(!user_filter) user_filter = 'cn';
var userDN = util.format("%s=%s,%s", user_filter, username, user_base);
console.log(userDN);
var bind_promised = Promise.promisify(ldap_client.bind, { context: ldap_client }); var bind_promised = Promise.promisify(ldap_client.bind, { context: ldap_client });
return bind_promised(userDN, password) return bind_promised(userDN, password)
.error(function(err) { .error(function(err) {
@ -20,16 +24,19 @@ function validateCredentials(ldap_client, username, password, users_dn) {
}); });
} }
function retrieve_email(ldap_client, username, users_dn) { function retrieve_email(ldap_client, username, user_base, user_filter) {
var userDN = util.format("cn=%s,%s", username, users_dn); // if not provided, default to cn
if(!user_filter) user_filter = 'cn';
var userDN = util.format("%s=%s,%s", user_filter, username, user_base);
console.log(userDN);
var search_promised = Promise.promisify(ldap_client.search, { context: ldap_client }); var search_promised = Promise.promisify(ldap_client.search, { context: ldap_client });
var query = {}; var query = {};
query.sizeLimit = 1; query.sizeLimit = 1;
query.attributes = ['mail']; query.attributes = ['mail'];
var base_dn = userDN;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
search_promised(base_dn, query) search_promised(userDN, query)
.then(function(res) { .then(function(res) {
var doc; var doc;
res.on('searchEntry', function(entry) { res.on('searchEntry', function(entry) {
@ -49,7 +56,12 @@ function retrieve_email(ldap_client, username, users_dn) {
} }
function update_password(ldap_client, ldap, username, new_password, config) { function update_password(ldap_client, ldap, username, new_password, config) {
var userDN = util.format("cn=%s,%s", username, config.ldap_users_dn); var user_filter = config.ldap_user_search_filter;
// if not provided, default to cn
if(!user_filter) user_filter = 'cn';
var userDN = util.format("%s=%s,%s", user_filter, username,
config.ldap_user_search_base);
var encoded_password = Dovehash.encode('SSHA', new_password); var encoded_password = Dovehash.encode('SSHA', new_password);
var change = new ldap.Change({ var change = new ldap.Change({
operation: 'replace', operation: 'replace',

View File

@ -23,18 +23,20 @@ function first_factor(req, res) {
logger.debug('1st factor: Start bind operation against LDAP'); logger.debug('1st factor: Start bind operation against LDAP');
logger.debug('1st factor: username=%s', username); logger.debug('1st factor: username=%s', username);
logger.debug('1st factor: base_dn=%s', config.ldap_users_dn); logger.debug('1st factor: base_dn=%s', config.ldap_user_search_base);
logger.debug('1st factor: user_filter=%s', config.ldap_user_search_filter);
regulator.regulate(username) regulator.regulate(username)
.then(function() { .then(function() {
return ldap.validate(ldap_client, username, password, config.ldap_users_dn); return ldap.validate(ldap_client, username, password, config.ldap_user_search_base, config.ldap_user_search_filter);
}) })
.then(function() { .then(function() {
objectPath.set(req, 'session.auth_session.userid', username); objectPath.set(req, 'session.auth_session.userid', username);
objectPath.set(req, 'session.auth_session.first_factor', true); objectPath.set(req, 'session.auth_session.first_factor', true);
logger.info('1st factor: LDAP binding successful'); logger.info('1st factor: LDAP binding successful');
logger.debug('1st factor: Retrieve email from LDAP'); logger.debug('1st factor: Retrieve email from LDAP');
return ldap.get_email(ldap_client, username, config.ldap_users_dn) return ldap.get_email(ldap_client, username, config.ldap_user_search_base,
config.ldap_user_search_filter)
}) })
.then(function(doc) { .then(function(doc) {
var email = objectPath.get(doc, 'mail'); var email = objectPath.get(doc, 'mail');

View File

@ -27,7 +27,8 @@ function pre_check(req) {
var ldap_client = req.app.get('ldap client'); var ldap_client = req.app.get('ldap client');
var config = req.app.get('config'); var config = req.app.get('config');
return ldap.get_email(ldap_client, userid, config.ldap_users_dn) return ldap.get_email(ldap_client, userid, config.ldap_user_search_base,
config.ldap_user_search_filter)
.then(function(doc) { .then(function(doc) {
var email = objectPath.get(doc, 'mail'); var email = objectPath.get(doc, 'mail');

View File

@ -18,7 +18,8 @@ describe('test the first factor validation route', function() {
search: sinon.stub() search: sinon.stub()
} }
var config = { var config = {
ldap_users_dn: 'dc=example,dc=com' ldap_user_search_base: 'ou=users,dc=example,dc=com',
ldap_user_search_filter: 'uid'
} }
var search_doc = { var search_doc = {
@ -42,7 +43,7 @@ describe('test the first factor validation route', function() {
var app_get = sinon.stub(); var app_get = sinon.stub();
app_get.withArgs('ldap client').returns(ldap_interface_mock); app_get.withArgs('ldap client').returns(ldap_interface_mock);
app_get.withArgs('config').returns(ldap_interface_mock); app_get.withArgs('config').returns(config);
app_get.withArgs('logger').returns(winston); app_get.withArgs('logger').returns(winston);
app_get.withArgs('authentication regulator').returns(regulator); app_get.withArgs('authentication regulator').returns(regulator);
@ -79,6 +80,21 @@ describe('test the first factor validation route', function() {
}); });
}); });
it('should bind user based on LDAP DN', function(done) {
ldap_interface_mock.bind = sinon.spy(function(dn) {
if(dn == 'uid=username,ou=users,dc=example,dc=com') done();
});
first_factor(req, res);
});
it('should retrieve email from LDAP', function(done) {
ldap_interface_mock.bind.yields(undefined);
ldap_interface_mock.search = sinon.spy(function(dn) {
if(dn == 'uid=username,ou=users,dc=example,dc=com') done();
});
first_factor(req, res);
});
it('should return status code 401 when LDAP binding fails', function(done) { it('should return status code 401 when LDAP binding fails', function(done) {
res.send = sinon.spy(function(data) { res.send = sinon.spy(function(data) {
assert.equal(401, res.status.getCall(0).args[0]); assert.equal(401, res.status.getCall(0).args[0]);

View File

@ -46,7 +46,8 @@ describe('test reset password', function() {
req.app.get.withArgs('ldap client').returns(ldap_client); req.app.get.withArgs('ldap client').returns(ldap_client);
config = {}; config = {};
config.ldap_users_dn = 'dc=example,dc=com'; config.ldap_user_search_base = 'dc=example,dc=com';
config.ldap_user_search_filter = 'cn';
req.app.get.withArgs('config').returns(config); req.app.get.withArgs('config').returns(config);
res = {}; res = {};
@ -75,6 +76,15 @@ describe('test reset password', function() {
}); });
}); });
it('should perform a search in ldap to find email address', function(done) {
config.ldap_user_search_filter = 'uid';
ldap_client.search = sinon.spy(function(dn) {
console.log(dn);
if(dn == 'uid=user,dc=example,dc=com') done();
});
reset_password.icheck_interface.pre_check_callback(req);
});
it('should returns identity when ldap replies', function(done) { it('should returns identity when ldap replies', function(done) {
var doc = {}; var doc = {};
doc.object = {}; doc.object = {};

View File

@ -54,7 +54,7 @@ describe('test data persistence', function() {
port: PORT, port: PORT,
totp_secret: 'totp_secret', totp_secret: 'totp_secret',
ldap_url: 'ldap://127.0.0.1:389', ldap_url: 'ldap://127.0.0.1:389',
ldap_users_dn: 'ou=users,dc=example,dc=com', ldap_user_search_base: 'ou=users,dc=example,dc=com',
session_secret: 'session_secret', session_secret: 'session_secret',
session_max_age: 50000, session_max_age: 50000,
store_directory: tmpDir.name, store_directory: tmpDir.name,

View File

@ -25,9 +25,8 @@ describe('test ldap validation', function() {
function test_validate() { function test_validate() {
var username = 'user'; var username = 'user';
var password = 'password'; var password = 'password';
var ldap_url = 'http://ldap';
var users_dn = 'dc=example,dc=com'; var users_dn = 'dc=example,dc=com';
return ldap.validate(ldap_client, username, password, ldap_url, users_dn); return ldap.validate(ldap_client, username, password, users_dn);
} }
it('should bind the user if good credentials provided', function() { it('should bind the user if good credentials provided', function() {
@ -35,6 +34,29 @@ describe('test ldap validation', function() {
return test_validate(); return test_validate();
}); });
it('should bind the user with correct DN', function(done) {
var username = 'user';
var password = 'password';
var user_search_base = 'dc=example,dc=com';
var user_search_filter = 'uid';
ldap_client.bind = sinon.spy(function(dn) {
if(dn == 'uid=user,dc=example,dc=com') done();
});
ldap.validate(ldap_client, username, password, user_search_base,
user_search_filter);
});
it('should default to cn user search filter if no filter provided', function(done) {
var username = 'user';
var password = 'password';
var user_search_base = 'dc=example,dc=com';
ldap_client.bind = sinon.spy(function(dn) {
if(dn == 'cn=user,dc=example,dc=com') done();
});
ldap.validate(ldap_client, username, password, user_search_base,
undefined);
});
// cover an issue with promisify context // cover an issue with promisify context
it('should promisify correctly', function() { it('should promisify correctly', function() {
function LdapClient() { function LdapClient() {
@ -76,6 +98,14 @@ describe('test ldap validation', function() {
}) })
}); });
it('should use the user filter', function(done) {
ldap_client.search = sinon.spy(function(dn) {
if(dn == 'uid=username,ou=users,dc=example,dc=com') done();
});
ldap.get_email(ldap_client, 'username', 'ou=users,dc=example,dc=com',
'uid')
});
it('should fail on error with search method', function(done) { it('should fail on error with search method', function(done) {
var expected_doc = {}; var expected_doc = {};
expected_doc.mail = []; expected_doc.mail = [];
@ -97,7 +127,7 @@ describe('test ldap validation', function() {
change.modification.userPassword = 'new-password'; change.modification.userPassword = 'new-password';
var config = {}; var config = {};
config.ldap_users_dn = 'dc=example,dc=com'; config.ldap_user_search_base = 'dc=example,dc=com';
config.ldap_user = 'admin'; config.ldap_user = 'admin';
var userdn = 'cn=user,dc=example,dc=com'; var userdn = 'cn=user,dc=example,dc=com';
@ -135,6 +165,22 @@ describe('test ldap validation', function() {
done(); done();
}) })
}); });
it('should use the user filter', function(done) {
var ldapjs = {};
ldapjs.Change = sinon.spy();
var config = {};
config.ldap_user_search_base = 'ou=users,dc=example,dc=com';
config.ldap_user_search_filter = 'uid';
config.ldap_user = 'admin';
ldap_client.bind.yields(undefined);
ldap_client.modify = sinon.spy(function(dn) {
if(dn == 'uid=username,ou=users,dc=example,dc=com') done();
});
ldap.update_password(ldap_client, ldapjs, 'username', 'newpass', config)
});
} }
}); });

View File

@ -33,7 +33,8 @@ describe('test the server', function() {
port: PORT, port: PORT,
totp_secret: 'totp_secret', totp_secret: 'totp_secret',
ldap_url: 'ldap://127.0.0.1:389', ldap_url: 'ldap://127.0.0.1:389',
ldap_users_dn: 'ou=users,dc=example,dc=com', ldap_user_search_base: 'ou=users,dc=example,dc=com',
ldap_user_search_filter: 'cn',
ldap_user: 'cn=admin,dc=example,dc=com', ldap_user: 'cn=admin,dc=example,dc=com',
ldap_password: 'password', ldap_password: 'password',
session_secret: 'session_secret', session_secret: 'session_secret',

View File

@ -4,9 +4,11 @@ var server = require('../../src/lib/server');
var assert = require('assert'); var assert = require('assert');
describe('test server configuration', function() { describe('test server configuration', function() {
it('should set cookie scope to domain set in the config', function() { var deps;
var config = {}; var config;
config.session_domain = 'example.com';
before(function() {
config = {};
config.notifier = { config.notifier = {
gmail: { gmail: {
user: 'user@example.com', user: 'user@example.com',
@ -22,13 +24,17 @@ describe('test server configuration', function() {
return transporter; return transporter;
  });   });
var deps = {}; deps = {};
deps.nedb = require('nedb'); deps.nedb = require('nedb');
deps.nodemailer = nodemailer; deps.nodemailer = nodemailer;
deps.session = sinon.spy(function() { deps.session = sinon.spy(function() {
return function(req, res, next) { next(); }; return function(req, res, next) { next(); };
}); });
});
it('should set cookie scope to domain set in the config', function() {
config.session_domain = 'example.com';
server.run(config, undefined, deps); server.run(config, undefined, deps);
assert(deps.session.calledOnce); assert(deps.session.calledOnce);