Add an LDAP user search filter in the configuration filte to specify the user attribute to search for in LDAP

pull/18/head
Clement Michaud 2017-03-16 01:25:55 +01:00
parent 95130f491c
commit c7e4f76b9c
12 changed files with 138 additions and 26 deletions

View File

@ -3,9 +3,11 @@
logs_level: info
# Configuration of LDAP
# Example: for user john, the DN will be cn=john,ou=users,dc=example,dc=com
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
password: password

View File

@ -29,3 +29,18 @@ uid: user
uidnumber: 1000
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 = {
port: process.env.PORT || 8080,
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_password: yaml_config.ldap.password,
session_domain: yaml_config.session.domain,

View File

@ -1,6 +1,6 @@
module.exports = {
validate: validateCredentials,
validate: validate_credentials,
get_email: retrieve_email,
update_password: update_password
}
@ -10,8 +10,12 @@ var Promise = require('bluebird');
var exceptions = require('./exceptions');
var Dovehash = require('dovehash');
function validateCredentials(ldap_client, username, password, users_dn) {
var userDN = util.format("cn=%s,%s", username, users_dn);
function validate_credentials(ldap_client, username, password, user_base, user_filter) {
// 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 });
return bind_promised(userDN, password)
.error(function(err) {
@ -20,16 +24,19 @@ function validateCredentials(ldap_client, username, password, users_dn) {
});
}
function retrieve_email(ldap_client, username, users_dn) {
var userDN = util.format("cn=%s,%s", username, users_dn);
function retrieve_email(ldap_client, username, user_base, user_filter) {
// 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 query = {};
query.sizeLimit = 1;
query.attributes = ['mail'];
var base_dn = userDN;
return new Promise(function(resolve, reject) {
search_promised(base_dn, query)
search_promised(userDN, query)
.then(function(res) {
var doc;
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) {
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 change = new ldap.Change({
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: 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)
.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() {
objectPath.set(req, 'session.auth_session.userid', username);
objectPath.set(req, 'session.auth_session.first_factor', true);
logger.info('1st factor: LDAP binding successful');
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) {
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 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) {
var email = objectPath.get(doc, 'mail');

View File

@ -18,7 +18,8 @@ describe('test the first factor validation route', function() {
search: sinon.stub()
}
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 = {
@ -42,7 +43,7 @@ describe('test the first factor validation route', function() {
var app_get = sinon.stub();
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('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) {
res.send = sinon.spy(function(data) {
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);
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);
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) {
var doc = {};
doc.object = {};

View File

@ -54,7 +54,7 @@ describe('test data persistence', function() {
port: PORT,
totp_secret: 'totp_secret',
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_max_age: 50000,
store_directory: tmpDir.name,

View File

@ -25,9 +25,8 @@ describe('test ldap validation', function() {
function test_validate() {
var username = 'user';
var password = 'password';
var ldap_url = 'http://ldap';
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() {
@ -35,6 +34,29 @@ describe('test ldap validation', function() {
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
it('should promisify correctly', function() {
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) {
var expected_doc = {};
expected_doc.mail = [];
@ -97,7 +127,7 @@ describe('test ldap validation', function() {
change.modification.userPassword = 'new-password';
var config = {};
config.ldap_users_dn = 'dc=example,dc=com';
config.ldap_user_search_base = 'dc=example,dc=com';
config.ldap_user = 'admin';
var userdn = 'cn=user,dc=example,dc=com';
@ -135,6 +165,22 @@ describe('test ldap validation', function() {
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,
totp_secret: 'totp_secret',
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_password: 'password',
session_secret: 'session_secret',

View File

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