Add the access_control entry in the config file to allow the user to define per group rules to access the subdomains
parent
4b93338bae
commit
2a73b1a431
|
@ -1,20 +1,57 @@
|
|||
|
||||
# The port to listen on
|
||||
port: 8080
|
||||
port: 80
|
||||
|
||||
# Log level
|
||||
#
|
||||
# Level of verbosity for logs
|
||||
logs_level: info
|
||||
|
||||
# Configuration of LDAP
|
||||
# LDAP configuration
|
||||
#
|
||||
# Example: for user john, the DN will be cn=john,ou=users,dc=example,dc=com
|
||||
ldap:
|
||||
# The url of the ldap server
|
||||
url: ldap://ldap
|
||||
user_search_base: ou=users,dc=example,dc=com
|
||||
user_search_filter: cn
|
||||
|
||||
# The base dn for every entries
|
||||
base_dn: dc=example,dc=com
|
||||
|
||||
# An additional dn to define the scope to all users
|
||||
additional_user_dn: ou=users
|
||||
|
||||
# The user name attribute of users. Might uid for FreeIPA. 'cn' by default.
|
||||
user_name_attribute: cn
|
||||
|
||||
# An additional dn to define the scope of groups
|
||||
additional_group_dn: ou=groups
|
||||
|
||||
# The group name attribute of group. 'cn' by default.
|
||||
group_name_attribute: cn
|
||||
|
||||
# The username and password of the admin user.
|
||||
user: cn=admin,dc=example,dc=com
|
||||
password: password
|
||||
|
||||
|
||||
# Access Control
|
||||
#
|
||||
# Access control is a set of rules where you can specify a group-based
|
||||
# subdomain restrictions.
|
||||
#
|
||||
# If access_control is not defined, ACL rules are disabled and default policy
|
||||
# is allowed to everyone.
|
||||
# Otherwise, the default policy is denied for any user and any subdomain.
|
||||
access_control:
|
||||
- group: admin
|
||||
allowed_domains:
|
||||
- secret.test.local
|
||||
- secret1.test.local
|
||||
- group: dev
|
||||
allowed_domains:
|
||||
- secret2.test.local
|
||||
|
||||
|
||||
# Configuration of session cookies
|
||||
#
|
||||
# _secret_ the secret to encrypt session cookies
|
||||
|
|
|
@ -16,6 +16,9 @@ services:
|
|||
- SLAPD_ORGANISATION=MyCompany
|
||||
- SLAPD_DOMAIN=example.com
|
||||
- SLAPD_PASSWORD=password
|
||||
- SLAPD_ADDITIONAL_MODULES=memberof
|
||||
- SLAPD_ADDITIONAL_SCHEMAS=openldap
|
||||
- SLAPD_FORCE_RECONFIGURE=true
|
||||
expose:
|
||||
- "389"
|
||||
volumes:
|
||||
|
|
|
@ -8,39 +8,55 @@ objectclass: organizationalUnit
|
|||
objectclass: top
|
||||
ou: users
|
||||
|
||||
dn: cn=user,ou=groups,dc=example,dc=com
|
||||
cn: user
|
||||
gidnumber: 502
|
||||
objectclass: posixGroup
|
||||
dn: cn=dev,ou=groups,dc=example,dc=com
|
||||
cn: dev
|
||||
member: cn=john,ou=users,dc=example,dc=com
|
||||
member: cn=bob,ou=users,dc=example,dc=com
|
||||
objectclass: groupOfNames
|
||||
objectclass: top
|
||||
|
||||
dn: cn=user,ou=users,dc=example,dc=com
|
||||
cn: user
|
||||
gidnumber: 500
|
||||
givenname: user
|
||||
homedirectory: /home/user1
|
||||
loginshell: /bin/sh
|
||||
objectclass: inetOrgPerson
|
||||
objectclass: posixAccount
|
||||
dn: cn=admin,ou=groups,dc=example,dc=com
|
||||
cn: admin
|
||||
member: cn=john,ou=users,dc=example,dc=com
|
||||
objectclass: groupOfNames
|
||||
objectclass: top
|
||||
mail: user@example.com
|
||||
sn: User
|
||||
uid: user
|
||||
uidnumber: 1000
|
||||
|
||||
dn: cn=john,ou=users,dc=example,dc=com
|
||||
cn: john
|
||||
objectclass: inetOrgPerson
|
||||
objectclass: top
|
||||
mail: john.doe@example.com
|
||||
sn: John Doe
|
||||
userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
|
||||
|
||||
dn: uid=useruid,ou=users,dc=example,dc=com
|
||||
cn: useruid
|
||||
gidnumber: 500
|
||||
givenname: user
|
||||
homedirectory: /home/user1
|
||||
loginshell: /bin/sh
|
||||
dn: cn=harry,ou=users,dc=example,dc=com
|
||||
cn: harry
|
||||
objectclass: inetOrgPerson
|
||||
objectclass: posixAccount
|
||||
objectclass: top
|
||||
mail: useruid@example.com
|
||||
sn: User
|
||||
uid: useruid
|
||||
uidnumber: 1001
|
||||
mail: harry.potter@example.com
|
||||
sn: Harry Potter
|
||||
userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
|
||||
|
||||
dn: cn=bob,ou=users,dc=example,dc=com
|
||||
cn: bob
|
||||
objectclass: inetOrgPerson
|
||||
objectclass: top
|
||||
mail: bob.dylan@example.com
|
||||
sn: Bob Dylan
|
||||
userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
|
||||
|
||||
# dn: uid=jack,ou=users,dc=example,dc=com
|
||||
# cn: jack
|
||||
# gidnumber: 501
|
||||
# givenname: Jack
|
||||
# homedirectory: /home/jack
|
||||
# loginshell: /bin/sh
|
||||
# objectclass: inetOrgPerson
|
||||
# objectclass: posixAccount
|
||||
# objectclass: top
|
||||
# mail: jack.daniels@example.com
|
||||
# sn: Jack Daniels
|
||||
# uid: jack
|
||||
# uidnumber: 1001
|
||||
# userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
|
||||
#
|
||||
|
|
|
@ -73,8 +73,8 @@ http {
|
|||
|
||||
location /authentication/verify {
|
||||
proxy_set_header X-Original-URI $request_uri;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_pass http://auth/authentication/verify;
|
||||
}
|
||||
|
|
19
src/index.js
19
src/index.js
|
@ -4,12 +4,14 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|||
|
||||
var server = require('./lib/server');
|
||||
|
||||
var ldap = require('ldapjs');
|
||||
var ldapjs = require('ldapjs');
|
||||
var u2f = require('authdog');
|
||||
var nodemailer = require('nodemailer');
|
||||
var nedb = require('nedb');
|
||||
var YAML = require('yamljs');
|
||||
var session = require('express-session');
|
||||
var winston = require('winston');
|
||||
var speakeasy = require('speakeasy');
|
||||
|
||||
var config_path = process.argv[2];
|
||||
if(!config_path) {
|
||||
|
@ -22,20 +24,13 @@ console.log('Parse configuration file: %s', config_path);
|
|||
|
||||
var yaml_config = YAML.load(config_path);
|
||||
|
||||
var ldap_client = ldap.createClient({
|
||||
url: config.ldap_url,
|
||||
reconnect: true
|
||||
});
|
||||
|
||||
ldap_client.on('error', function(err) {
|
||||
console.error('LDAP Error:', err.message)
|
||||
})
|
||||
|
||||
var deps = {};
|
||||
deps.u2f = u2f;
|
||||
deps.nedb = nedb;
|
||||
deps.nodemailer = nodemailer;
|
||||
deps.ldap = ldap;
|
||||
deps.ldapjs = ldapjs;
|
||||
deps.session = session;
|
||||
deps.winston = winston;
|
||||
deps.speakeasy = speakeasy;
|
||||
|
||||
server.run(yaml_config, ldap_client, deps);
|
||||
server.run(yaml_config, deps);
|
||||
|
|
|
@ -4,17 +4,14 @@ var objectPath = require('object-path');
|
|||
module.exports = function(yaml_config) {
|
||||
return {
|
||||
port: objectPath.get(yaml_config, 'port', 8080),
|
||||
ldap_url: objectPath.get(yaml_config, 'ldap.url', 'ldap://127.0.0.1:389'),
|
||||
ldap_user_search_base: objectPath.get(yaml_config, 'ldap.user_search_base'),
|
||||
ldap_user_search_filter: objectPath.get(yaml_config, 'ldap.user_search_filter'),
|
||||
ldap_user: objectPath.get(yaml_config, 'ldap.user'),
|
||||
ldap_password: objectPath.get(yaml_config, 'ldap.password'),
|
||||
ldap: objectPath.get(yaml_config, 'ldap', 'ldap://127.0.0.1:389'),
|
||||
session_domain: objectPath.get(yaml_config, 'session.domain'),
|
||||
session_secret: objectPath.get(yaml_config, 'session.secret'),
|
||||
session_max_age: objectPath.get(yaml_config, 'session.expiration', 3600000), // in ms
|
||||
store_directory: objectPath.get(yaml_config, 'store_directory'),
|
||||
logs_level: objectPath.get(yaml_config, 'logs_level'),
|
||||
notifier: objectPath.get(yaml_config, 'notifier'),
|
||||
access_control: objectPath.get(yaml_config, 'access_control')
|
||||
}
|
||||
};
|
||||
|
||||
|
|
150
src/lib/ldap.js
150
src/lib/ldap.js
|
@ -1,46 +1,66 @@
|
|||
|
||||
module.exports = {
|
||||
validate: validate_credentials,
|
||||
get_email: retrieve_email,
|
||||
update_password: update_password
|
||||
}
|
||||
module.exports = Ldap;
|
||||
|
||||
var util = require('util');
|
||||
var Promise = require('bluebird');
|
||||
var exceptions = require('./exceptions');
|
||||
var Dovehash = require('dovehash');
|
||||
|
||||
function validate_credentials(ldap_client, username, password, user_base, user_filter) {
|
||||
// if not provided, default to cn
|
||||
if(!user_filter) user_filter = 'cn';
|
||||
function Ldap(deps, ldap_config) {
|
||||
this.ldap_config = ldap_config;
|
||||
|
||||
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)
|
||||
this.ldapjs = deps.ldapjs;
|
||||
this.logger = deps.winston;
|
||||
|
||||
this.connect();
|
||||
}
|
||||
|
||||
Ldap.prototype.connect = function() {
|
||||
var ldap_client = this.ldapjs.createClient({
|
||||
url: this.ldap_config.url,
|
||||
reconnect: true
|
||||
});
|
||||
|
||||
ldap_client.on('error', function(err) {
|
||||
console.error('LDAP Error:', err.message)
|
||||
});
|
||||
|
||||
this.ldap_client = Promise.promisifyAll(ldap_client);
|
||||
}
|
||||
|
||||
Ldap.prototype._build_user_dn = function(username) {
|
||||
var user_name_attr = this.ldap_config.user_name_attribute;
|
||||
// if not provided, default to cn
|
||||
if(!user_name_attr) user_name_attr = 'cn';
|
||||
|
||||
var additional_user_dn = this.ldap_config.additional_user_dn;
|
||||
var base_dn = this.ldap_config.base_dn;
|
||||
|
||||
var user_dn = util.format("%s=%s", user_name_attr, username);
|
||||
if(additional_user_dn) user_dn += util.format(",%s", additional_user_dn);
|
||||
user_dn += util.format(',%s', base_dn);
|
||||
return user_dn;
|
||||
}
|
||||
|
||||
Ldap.prototype.bind = function(username, password) {
|
||||
var user_dn = this._build_user_dn(username);
|
||||
|
||||
this.logger.debug('LDAP: Bind user %s', user_dn);
|
||||
return this.ldap_client.bindAsync(user_dn, password)
|
||||
.error(function(err) {
|
||||
console.error(err);
|
||||
throw new exceptions.LdapBindError(err.message);
|
||||
});
|
||||
}
|
||||
|
||||
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'];
|
||||
|
||||
Ldap.prototype._search_in_ldap = function(base, query) {
|
||||
var that = this;
|
||||
this.logger.debug('LDAP: Search for %s in %s', JSON.stringify(query), base);
|
||||
return new Promise(function(resolve, reject) {
|
||||
search_promised(userDN, query)
|
||||
that.ldap_client.searchAsync(base, query)
|
||||
.then(function(res) {
|
||||
var doc;
|
||||
var doc = [];
|
||||
res.on('searchEntry', function(entry) {
|
||||
doc = entry.object;
|
||||
doc.push(entry.object);
|
||||
});
|
||||
res.on('error', function(err) {
|
||||
reject(new exceptions.LdapSearchError(err));
|
||||
|
@ -55,26 +75,80 @@ function retrieve_email(ldap_client, username, user_base, user_filter) {
|
|||
});
|
||||
}
|
||||
|
||||
function update_password(ldap_client, ldap, username, new_password, config) {
|
||||
var user_filter = config.ldap_user_search_filter;
|
||||
// if not provided, default to cn
|
||||
if(!user_filter) user_filter = 'cn';
|
||||
Ldap.prototype.get_groups = function(username) {
|
||||
var user_dn = this._build_user_dn(username);
|
||||
|
||||
var group_name_attr = this.ldap_config.group_name_attribute;
|
||||
if(!group_name_attr) group_name_attr = 'cn';
|
||||
|
||||
var additional_group_dn = this.ldap_config.additional_group_dn;
|
||||
var base_dn = this.ldap_config.base_dn;
|
||||
|
||||
var group_dn = base_dn;
|
||||
if(additional_group_dn)
|
||||
group_dn = util.format('%s,', additional_group_dn) + group_dn;
|
||||
|
||||
var query = {};
|
||||
query.scope = 'sub';
|
||||
query.attributes = [group_name_attr];
|
||||
query.filter = 'member=' + user_dn ;
|
||||
|
||||
var that = this;
|
||||
this.logger.debug('LDAP: get groups of user %s', username);
|
||||
return this._search_in_ldap(group_dn, query)
|
||||
.then(function(docs) {
|
||||
var groups = [];
|
||||
for(var i = 0; i<docs.length; ++i) {
|
||||
groups.push(docs[i].cn);
|
||||
}
|
||||
that.logger.debug('LDAP: got groups %s', groups);
|
||||
return Promise.resolve(groups);
|
||||
});
|
||||
}
|
||||
|
||||
Ldap.prototype.get_emails = function(username) {
|
||||
var that = this;
|
||||
var user_dn = this._build_user_dn(username);
|
||||
|
||||
var query = {};
|
||||
query.scope = 'base';
|
||||
query.sizeLimit = 1;
|
||||
query.attributes = ['mail'];
|
||||
|
||||
this.logger.debug('LDAP: get emails of user %s', username);
|
||||
return this._search_in_ldap(user_dn, query)
|
||||
.then(function(docs) {
|
||||
var emails = [];
|
||||
for(var i = 0; i<docs.length; ++i) {
|
||||
if(typeof docs[i].mail === 'string')
|
||||
emails.push(docs[i].mail);
|
||||
else {
|
||||
emails.concat(docs[i].mail);
|
||||
}
|
||||
}
|
||||
that.logger.debug('LDAP: got emails %s', emails);
|
||||
return Promise.resolve(emails);
|
||||
});
|
||||
}
|
||||
|
||||
Ldap.prototype.update_password = function(username, new_password) {
|
||||
var user_dn = this._build_user_dn(username);
|
||||
|
||||
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({
|
||||
var change = new this.ldapjs.Change({
|
||||
operation: 'replace',
|
||||
modification: {
|
||||
userPassword: encoded_password
|
||||
}
|
||||
});
|
||||
|
||||
var modify_promised = Promise.promisify(ldap_client.modify, { context: ldap_client });
|
||||
var bind_promised = Promise.promisify(ldap_client.bind, { context: ldap_client });
|
||||
var that = this;
|
||||
this.logger.debug('LDAP: update password of user %s', username);
|
||||
|
||||
return bind_promised(config.ldap_user, config.ldap_password)
|
||||
this.logger.debug('LDAP: bind admin');
|
||||
return this.ldap_client.bindAsync(this.ldap_config.user, this.ldap_config.password)
|
||||
.then(function() {
|
||||
return modify_promised(userDN, change);
|
||||
that.logger.debug('LDAP: modify password');
|
||||
return that.ldap_client.modifyAsync(user_dn, change);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,11 +2,25 @@
|
|||
module.exports = first_factor;
|
||||
|
||||
var exceptions = require('../exceptions');
|
||||
var ldap = require('../ldap');
|
||||
var objectPath = require('object-path');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
function get_allowed_domains(access_control, groups) {
|
||||
var allowed_domains = [];
|
||||
|
||||
for(var i = 0; i<access_control.length; ++i) {
|
||||
var rule = access_control[i];
|
||||
if('group' in rule && 'allowed_domains' in rule) {
|
||||
if(groups.indexOf(rule['group']) >= 0) {
|
||||
var domains = rule.allowed_domains;
|
||||
allowed_domains = allowed_domains.concat(domains);
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed_domains;
|
||||
}
|
||||
|
||||
function first_factor(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
if(!username || !password) {
|
||||
|
@ -15,59 +29,69 @@ function first_factor(req, res) {
|
|||
return;
|
||||
}
|
||||
|
||||
logger.info('1st factor: Starting authentication of user "%s"', username);
|
||||
|
||||
var ldap_client = req.app.get('ldap client');
|
||||
var logger = req.app.get('logger');
|
||||
var ldap = req.app.get('ldap');
|
||||
var config = req.app.get('config');
|
||||
var regulator = req.app.get('authentication regulator');
|
||||
|
||||
logger.info('1st factor: Starting authentication of user "%s"', username);
|
||||
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_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_user_search_base, config.ldap_user_search_filter);
|
||||
return ldap.bind(username, password);
|
||||
})
|
||||
.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_user_search_base,
|
||||
config.ldap_user_search_filter)
|
||||
return Promise.join(ldap.get_emails(username), ldap.get_groups(username));
|
||||
})
|
||||
.then(function(doc) {
|
||||
var email = objectPath.get(doc, 'mail');
|
||||
logger.debug('1st factor: document=%s', JSON.stringify(doc));
|
||||
logger.debug('1st factor: Retrieved email is %s', email);
|
||||
.then(function(data) {
|
||||
var emails = data[0];
|
||||
var groups = data[1];
|
||||
|
||||
if(!emails && emails.length <= 0) throw new Error('No email found');
|
||||
logger.debug('1st factor: Retrieved email are %s', emails);
|
||||
objectPath.set(req, 'session.auth_session.email', emails[0]);
|
||||
|
||||
if(config.access_control) {
|
||||
var allowed_domains = get_allowed_domains(config.access_control, groups);
|
||||
logger.debug('1st factor: allowed domains are %s', allowed_domains);
|
||||
objectPath.set(req, 'session.auth_session.allowed_domains',
|
||||
allowed_domains);
|
||||
}
|
||||
else {
|
||||
logger.debug('1st factor: no access control rules found.' +
|
||||
'Default policy to allow all.');
|
||||
}
|
||||
|
||||
objectPath.set(req, 'session.auth_session.email', email);
|
||||
regulator.mark(username, true);
|
||||
res.status(204);
|
||||
res.send();
|
||||
})
|
||||
.catch(exceptions.LdapSearchError, function(err) {
|
||||
logger.info('1st factor: Unable to retrieve email from LDAP', err);
|
||||
logger.error('1st factor: Unable to retrieve email from LDAP', err);
|
||||
res.status(500);
|
||||
res.send();
|
||||
})
|
||||
.catch(exceptions.LdapBindError, function(err) {
|
||||
logger.info('1st factor: LDAP binding failed');
|
||||
logger.error('1st factor: LDAP binding failed');
|
||||
logger.debug('1st factor: LDAP binding failed due to ', err);
|
||||
regulator.mark(username, false);
|
||||
res.status(401);
|
||||
res.send('Bad credentials');
|
||||
})
|
||||
.catch(exceptions.AuthenticationRegulationError, function(err) {
|
||||
logger.info('1st factor: the regulator rejected the authentication of user %s', username);
|
||||
logger.error('1st factor: the regulator rejected the authentication of user %s', username);
|
||||
logger.debug('1st factor: authentication rejected due to %s', err);
|
||||
res.status(403);
|
||||
res.send('Access has been restricted for a few minutes...');
|
||||
})
|
||||
.catch(function(err) {
|
||||
logger.debug('1st factor: Unhandled error %s', err);
|
||||
logger.error('1st factor: Unhandled error %s', err);
|
||||
res.status(500);
|
||||
res.send('Internal error');
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
var Promise = require('bluebird');
|
||||
var objectPath = require('object-path');
|
||||
var ldap = require('../ldap');
|
||||
var exceptions = require('../exceptions');
|
||||
var CHALLENGE = 'reset-password';
|
||||
|
||||
|
@ -24,16 +23,14 @@ function pre_check(req) {
|
|||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
var ldap_client = req.app.get('ldap client');
|
||||
var config = req.app.get('config');
|
||||
var ldap = req.app.get('ldap');
|
||||
|
||||
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');
|
||||
return ldap.get_emails(userid)
|
||||
.then(function(emails) {
|
||||
if(!emails && emails.length <= 0) throw new Error('No email found');
|
||||
|
||||
var identity = {}
|
||||
identity.email = email;
|
||||
identity.email = emails[0];
|
||||
identity.userid = userid;
|
||||
return Promise.resolve(identity);
|
||||
});
|
||||
|
@ -53,15 +50,13 @@ function protect(fn) {
|
|||
|
||||
function post(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
var ldapjs = req.app.get('ldap');
|
||||
var ldap_client = req.app.get('ldap client');
|
||||
var ldap = req.app.get('ldap');
|
||||
var new_password = objectPath.get(req, 'body.password');
|
||||
var userid = objectPath.get(req, 'session.auth_session.identity_check.userid');
|
||||
var config = req.app.get('config');
|
||||
|
||||
logger.info('POST reset-password: User %s wants to reset his/her password', userid);
|
||||
|
||||
ldap.update_password(ldap_client, ldapjs, userid, new_password, config)
|
||||
ldap.update_password(userid, new_password)
|
||||
.then(function() {
|
||||
logger.info('POST reset-password: Password reset for user %s', userid);
|
||||
objectPath.set(req, 'session.auth_session', undefined);
|
||||
|
|
|
@ -5,6 +5,8 @@ var objectPath = require('object-path');
|
|||
var Promise = require('bluebird');
|
||||
|
||||
function verify_filter(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
|
||||
if(!objectPath.has(req, 'session.auth_session'))
|
||||
return Promise.reject('No auth_session variable');
|
||||
|
||||
|
@ -14,6 +16,23 @@ function verify_filter(req, res) {
|
|||
if(!objectPath.has(req, 'session.auth_session.second_factor'))
|
||||
return Promise.reject('No second factor variable');
|
||||
|
||||
if(!objectPath.has(req, 'session.auth_session.userid'))
|
||||
return Promise.reject('No userid variable');
|
||||
|
||||
var config = req.app.get('config');
|
||||
var access_control = config.access_control;
|
||||
|
||||
if(access_control) {
|
||||
var allowed_domains = objectPath.get(req, 'session.auth_session.allowed_domains');
|
||||
var host = objectPath.get(req, 'headers.host');
|
||||
var domain = host.split(':')[0];
|
||||
logger.debug('Trying to access domain: %s', domain);
|
||||
logger.debug('User has access to %s', JSON.stringify(allowed_domains));
|
||||
|
||||
if(allowed_domains.indexOf(domain) < 0)
|
||||
return Promise.reject('Access restricted by ACL rules');
|
||||
}
|
||||
|
||||
if(!req.session.auth_session.first_factor ||
|
||||
!req.session.auth_session.second_factor)
|
||||
return Promise.reject('First or second factor not validated');
|
||||
|
@ -28,6 +47,7 @@ function verify(req, res) {
|
|||
res.send();
|
||||
})
|
||||
.catch(function(err) {
|
||||
req.app.get('logger').error(err);
|
||||
res.status(401);
|
||||
res.send();
|
||||
});
|
||||
|
|
|
@ -5,16 +5,15 @@ module.exports = {
|
|||
|
||||
var express = require('express');
|
||||
var bodyParser = require('body-parser');
|
||||
var speakeasy = require('speakeasy');
|
||||
var path = require('path');
|
||||
var winston = require('winston');
|
||||
var UserDataStore = require('./user_data_store');
|
||||
var Notifier = require('./notifier');
|
||||
var AuthenticationRegulator = require('./authentication_regulator');
|
||||
var setup_endpoints = require('./setup_endpoints');
|
||||
var config_adapter = require('./config_adapter');
|
||||
var Ldap = require('./ldap');
|
||||
|
||||
function run(yaml_config, ldap_client, deps, fn) {
|
||||
function run(yaml_config, deps, fn) {
|
||||
var config = config_adapter(yaml_config);
|
||||
|
||||
var view_directory = path.resolve(__dirname, '../views');
|
||||
|
@ -45,17 +44,17 @@ function run(yaml_config, ldap_client, deps, fn) {
|
|||
app.set('view engine', 'ejs');
|
||||
|
||||
// by default the level of logs is info
|
||||
winston.level = config.logs_level || 'info';
|
||||
deps.winston.level = config.logs_level || 'info';
|
||||
|
||||
var five_minutes = 5 * 60;
|
||||
var data_store = new UserDataStore(deps.nedb, datastore_options);
|
||||
var regulator = new AuthenticationRegulator(data_store, five_minutes);
|
||||
var notifier = new Notifier(config.notifier, deps);
|
||||
var ldap = new Ldap(deps, config.ldap);
|
||||
|
||||
app.set('logger', winston);
|
||||
app.set('ldap', deps.ldap);
|
||||
app.set('ldap client', ldap_client);
|
||||
app.set('totp engine', speakeasy);
|
||||
app.set('logger', deps.winston);
|
||||
app.set('ldap', ldap);
|
||||
app.set('totp engine', deps.speakeasy);
|
||||
app.set('u2f', deps.u2f);
|
||||
app.set('user data store', data_store);
|
||||
app.set('notifier', notifier);
|
||||
|
|
|
@ -5,34 +5,27 @@ var assert = require('assert');
|
|||
var winston = require('winston');
|
||||
var first_factor = require('../../../src/lib/routes/first_factor');
|
||||
var exceptions = require('../../../src/lib/exceptions');
|
||||
var Ldap = require('../../../src/lib/ldap');
|
||||
|
||||
describe('test the first factor validation route', function() {
|
||||
var req, res;
|
||||
var ldap_interface_mock;
|
||||
var emails;
|
||||
var search_res_ok;
|
||||
var regulator;
|
||||
var config;
|
||||
|
||||
beforeEach(function() {
|
||||
ldap_interface_mock = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub()
|
||||
}
|
||||
var config = {
|
||||
ldap_user_search_base: 'ou=users,dc=example,dc=com',
|
||||
ldap_user_search_filter: 'uid'
|
||||
}
|
||||
|
||||
var search_doc = {
|
||||
object: {
|
||||
mail: 'test_ok@example.com'
|
||||
ldap_interface_mock = sinon.createStubInstance(Ldap);
|
||||
config = {
|
||||
ldap: {
|
||||
base_dn: 'ou=users,dc=example,dc=com',
|
||||
user_name_attribute: 'uid'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var search_res_ok = {};
|
||||
search_res_ok.on = sinon.spy(function(event, fn) {
|
||||
if(event != 'error') fn(search_doc);
|
||||
});
|
||||
ldap_interface_mock.search.yields(undefined, search_res_ok);
|
||||
emails = [ 'test_ok@example.com' ];
|
||||
groups = [ 'group1', 'group2' ];
|
||||
|
||||
regulator = {};
|
||||
regulator.mark = sinon.stub();
|
||||
|
@ -42,7 +35,7 @@ describe('test the first factor validation route', function() {
|
|||
regulator.regulate.returns(Promise.resolve());
|
||||
|
||||
var app_get = sinon.stub();
|
||||
app_get.withArgs('ldap client').returns(ldap_interface_mock);
|
||||
app_get.withArgs('ldap').returns(ldap_interface_mock);
|
||||
app_get.withArgs('config').returns(config);
|
||||
app_get.withArgs('logger').returns(winston);
|
||||
app_get.withArgs('authentication regulator').returns(regulator);
|
||||
|
@ -75,43 +68,69 @@ describe('test the first factor validation route', function() {
|
|||
assert.equal(204, res.status.getCall(0).args[0]);
|
||||
resolve();
|
||||
});
|
||||
ldap_interface_mock.bind.yields(undefined);
|
||||
ldap_interface_mock.bind.withArgs('username').returns(Promise.resolve());
|
||||
ldap_interface_mock.get_emails.returns(Promise.resolve(emails));
|
||||
first_factor(req, res);
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
it('should store the allowed domains in the auth session', function() {
|
||||
config.access_control = [];
|
||||
config.access_control.push({
|
||||
group: 'group1',
|
||||
allowed_domains: ['domain1.example.com', 'domain2.example.com']
|
||||
});
|
||||
return new Promise(function(resolve, reject) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.deepEqual(['domain1.example.com', 'domain2.example.com'],
|
||||
req.session.auth_session.allowed_domains);
|
||||
assert.equal(204, res.status.getCall(0).args[0]);
|
||||
resolve();
|
||||
});
|
||||
ldap_interface_mock.bind.withArgs('username').returns(Promise.resolve());
|
||||
ldap_interface_mock.get_emails.returns(Promise.resolve(emails));
|
||||
ldap_interface_mock.get_groups.returns(Promise.resolve(groups));
|
||||
first_factor(req, res);
|
||||
});
|
||||
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();
|
||||
});
|
||||
res.send = sinon.spy(function(data) { done(); });
|
||||
ldap_interface_mock.bind.returns(Promise.resolve());
|
||||
ldap_interface_mock.get_emails = sinon.stub().withArgs('usernam').returns(Promise.resolve([{mail: ['test@example.com'] }]));
|
||||
first_factor(req, res);
|
||||
});
|
||||
|
||||
it('should return status code 401 when LDAP binding fails', function(done) {
|
||||
it('should set email as session variables', function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal('test_ok@example.com', req.session.auth_session.email);
|
||||
resolve();
|
||||
});
|
||||
var emails = [ 'test_ok@example.com' ];
|
||||
ldap_interface_mock.bind.returns(Promise.resolve());
|
||||
ldap_interface_mock.get_emails.returns(Promise.resolve(emails));
|
||||
first_factor(req, res);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return status code 401 when LDAP binding throws', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(401, res.status.getCall(0).args[0]);
|
||||
assert.equal(regulator.mark.getCall(0).args[0], 'username');
|
||||
done();
|
||||
});
|
||||
ldap_interface_mock.bind.yields('Bad credentials');
|
||||
ldap_interface_mock.bind.throws(new exceptions.LdapBindError('Bad credentials'));
|
||||
first_factor(req, res);
|
||||
});
|
||||
|
||||
it('should return status code 500 when LDAP binding throws', function(done) {
|
||||
it('should return status code 500 when LDAP search throws', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(500, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
ldap_interface_mock.bind.yields(undefined);
|
||||
ldap_interface_mock.search.yields('error');
|
||||
ldap_interface_mock.bind.returns(Promise.resolve());
|
||||
ldap_interface_mock.get_emails.throws(new exceptions.LdapSearchError('err'));
|
||||
first_factor(req, res);
|
||||
});
|
||||
|
||||
|
@ -122,8 +141,8 @@ describe('test the first factor validation route', function() {
|
|||
assert.equal(403, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
ldap_interface_mock.bind.yields(undefined);
|
||||
ldap_interface_mock.search.yields(undefined);
|
||||
ldap_interface_mock.bind.returns(Promise.resolve());
|
||||
ldap_interface_mock.get_emails.returns(Promise.resolve());
|
||||
first_factor(req, res);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
var reset_password = require('../../../src/lib/routes/reset_password');
|
||||
var Ldap = require('../../../src/lib/ldap');
|
||||
|
||||
var sinon = require('sinon');
|
||||
var winston = require('winston');
|
||||
var reset_password = require('../../../src/lib/routes/reset_password');
|
||||
var assert = require('assert');
|
||||
|
||||
describe('test reset password', function() {
|
||||
|
@ -35,20 +37,30 @@ describe('test reset password', function() {
|
|||
user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({}));
|
||||
req.app.get.withArgs('user data store').returns(user_data_store);
|
||||
|
||||
ldap = {};
|
||||
ldap.Change = sinon.spy();
|
||||
req.app.get.withArgs('ldap').returns(ldap);
|
||||
|
||||
config = {};
|
||||
config.ldap = {};
|
||||
config.ldap.base_dn = 'dc=example,dc=com';
|
||||
config.ldap.user_name_attribute = 'cn';
|
||||
req.app.get.withArgs('config').returns(config);
|
||||
|
||||
ldap_client = {};
|
||||
ldap_client.bind = sinon.stub();
|
||||
ldap_client.search = sinon.stub();
|
||||
ldap_client.modify = sinon.stub();
|
||||
req.app.get.withArgs('ldap client').returns(ldap_client);
|
||||
ldap_client.on = sinon.spy();
|
||||
|
||||
config = {};
|
||||
config.ldap_user_search_base = 'dc=example,dc=com';
|
||||
config.ldap_user_search_filter = 'cn';
|
||||
req.app.get.withArgs('config').returns(config);
|
||||
ldapjs = {};
|
||||
ldapjs.Change = sinon.spy();
|
||||
ldapjs.createClient = sinon.spy(function() {
|
||||
return ldap_client;
|
||||
});
|
||||
|
||||
deps = {
|
||||
ldapjs: ldapjs,
|
||||
winston: winston
|
||||
};
|
||||
req.app.get.withArgs('ldap').returns(new Ldap(deps, config.ldap));
|
||||
|
||||
res = {};
|
||||
res.send = sinon.spy();
|
||||
|
@ -77,9 +89,8 @@ describe('test reset password', function() {
|
|||
});
|
||||
|
||||
it('should perform a search in ldap to find email address', function(done) {
|
||||
config.ldap_user_search_filter = 'uid';
|
||||
config.ldap.user_name_attribute = '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);
|
||||
|
@ -88,7 +99,7 @@ describe('test reset password', function() {
|
|||
it('should returns identity when ldap replies', function(done) {
|
||||
var doc = {};
|
||||
doc.object = {};
|
||||
doc.object.email = 'test@example.com';
|
||||
doc.object.email = ['test@example.com'];
|
||||
doc.object.userid = 'user';
|
||||
|
||||
var res = {};
|
||||
|
|
|
@ -2,19 +2,33 @@
|
|||
var assert = require('assert');
|
||||
var verify = require('../../../src/lib/routes/verify');
|
||||
var sinon = require('sinon');
|
||||
var winston = require('winston');
|
||||
|
||||
describe('test authentication token verification', function() {
|
||||
var req, res;
|
||||
var config_mock;
|
||||
|
||||
beforeEach(function() {
|
||||
config_mock = {};
|
||||
req = {};
|
||||
res = {};
|
||||
req.headers = {};
|
||||
req.headers.host = 'secret.example.com';
|
||||
req.app = {};
|
||||
req.app.get = sinon.stub();
|
||||
req.app.get.withArgs('config').returns(config_mock);
|
||||
req.app.get.withArgs('logger').returns(winston);
|
||||
res.status = sinon.spy();
|
||||
});
|
||||
|
||||
it('should be already authenticated', function(done) {
|
||||
req.session = {};
|
||||
req.session.auth_session = {first_factor: true, second_factor: true};
|
||||
req.session.auth_session = {
|
||||
first_factor: true,
|
||||
second_factor: true,
|
||||
userid: 'myuser',
|
||||
group: 'mygroup'
|
||||
};
|
||||
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(204, res.status.getCall(0).args[0]);
|
||||
|
@ -25,13 +39,13 @@ describe('test authentication token verification', function() {
|
|||
});
|
||||
|
||||
describe('given different cases of session', function() {
|
||||
function test_unauthorized(auth_session) {
|
||||
function test_session(auth_session, status_code) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
req.session = {};
|
||||
req.session.auth_session = auth_session;
|
||||
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(401, res.status.getCall(0).args[0]);
|
||||
assert.equal(status_code, res.status.getCall(0).args[0]);
|
||||
resolve();
|
||||
});
|
||||
|
||||
|
@ -39,6 +53,14 @@ describe('test authentication token verification', function() {
|
|||
});
|
||||
}
|
||||
|
||||
function test_unauthorized(auth_session) {
|
||||
return test_session(auth_session, 401);
|
||||
}
|
||||
|
||||
function test_authorized(auth_session) {
|
||||
return test_session(auth_session, 204);
|
||||
}
|
||||
|
||||
it('should not be authenticated when second factor is missing', function() {
|
||||
return test_unauthorized({ first_factor: true, second_factor: false });
|
||||
});
|
||||
|
@ -47,6 +69,14 @@ describe('test authentication token verification', function() {
|
|||
return test_unauthorized({ first_factor: false, second_factor: true });
|
||||
});
|
||||
|
||||
it('should not be authenticated when userid is missing', function() {
|
||||
return test_unauthorized({
|
||||
first_factor: true,
|
||||
second_factor: true,
|
||||
group: 'mygroup',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be authenticated when first and second factor are missing', function() {
|
||||
return test_unauthorized({ first_factor: false, second_factor: false });
|
||||
});
|
||||
|
@ -55,6 +85,34 @@ describe('test authentication token verification', function() {
|
|||
return test_unauthorized(undefined);
|
||||
});
|
||||
|
||||
it('should reply unauthorized when the domain is restricted', function() {
|
||||
config_mock.access_control = [];
|
||||
config_mock.access_control.push({
|
||||
group: 'abc',
|
||||
allowed_domains: ['secret.example.com']
|
||||
});
|
||||
return test_unauthorized({
|
||||
first_factor: true,
|
||||
second_factor: true,
|
||||
userid: 'user',
|
||||
allowed_domains: ['restricted.example.com']
|
||||
});
|
||||
});
|
||||
|
||||
it('should reply authorized when the domain is allowed', function() {
|
||||
config_mock.access_control = [];
|
||||
config_mock.access_control.push({
|
||||
group: 'abc',
|
||||
allowed_domains: ['secret.example.com']
|
||||
});
|
||||
return test_authorized({
|
||||
first_factor: true,
|
||||
second_factor: true,
|
||||
userid: 'user',
|
||||
allowed_domains: ['secret.example.com']
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be authenticated when session is partially initialized', function() {
|
||||
return test_unauthorized({ first_factor: true });
|
||||
});
|
||||
|
|
|
@ -27,11 +27,11 @@ describe('test config adapter', function() {
|
|||
|
||||
var config = config_adapter(yaml_config);
|
||||
|
||||
assert.equal(config.ldap_url, 'http://ldap');
|
||||
assert.equal(config.ldap_user_search_base, 'ou=groups,dc=example,dc=com');
|
||||
assert.equal(config.ldap_user_search_filter, 'uid');
|
||||
assert.equal(config.ldap_user, 'admin');
|
||||
assert.equal(config.ldap_password, 'pass');
|
||||
assert.equal(config.ldap.url, 'http://ldap');
|
||||
assert.equal(config.ldap.user_search_base, 'ou=groups,dc=example,dc=com');
|
||||
assert.equal(config.ldap.user_search_filter, 'uid');
|
||||
assert.equal(config.ldap.user, 'admin');
|
||||
assert.equal(config.ldap.password, 'pass');
|
||||
});
|
||||
|
||||
it('should get the session attributes', function() {
|
||||
|
@ -64,4 +64,13 @@ describe('test config adapter', function() {
|
|||
|
||||
assert.equal(config.notifier, 'notifier');
|
||||
});
|
||||
|
||||
it('should get the access_control config', function() {
|
||||
yaml_config = {};
|
||||
yaml_config.access_control = 'access_control';
|
||||
|
||||
var config = config_adapter(yaml_config);
|
||||
|
||||
assert.equal(config.access_control, 'access_control');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ var sinon = require('sinon');
|
|||
var tmp = require('tmp');
|
||||
var nedb = require('nedb');
|
||||
var session = require('express-session');
|
||||
var winston = require('winston');
|
||||
|
||||
var PORT = 8050;
|
||||
var BASE_URL = 'http://localhost:' + PORT;
|
||||
|
@ -21,8 +22,14 @@ describe('test data persistence', function() {
|
|||
var tmpDir;
|
||||
var ldap_client = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub()
|
||||
search: sinon.stub(),
|
||||
on: sinon.spy()
|
||||
};
|
||||
var ldap = {
|
||||
createClient: sinon.spy(function() {
|
||||
return ldap_client;
|
||||
})
|
||||
}
|
||||
var config;
|
||||
|
||||
before(function() {
|
||||
|
@ -55,7 +62,7 @@ describe('test data persistence', function() {
|
|||
totp_secret: 'totp_secret',
|
||||
ldap: {
|
||||
url: 'ldap://127.0.0.1:389',
|
||||
user_search_base: 'ou=users,dc=example,dc=com',
|
||||
base_dn: 'ou=users,dc=example,dc=com',
|
||||
},
|
||||
session: {
|
||||
secret: 'session_secret',
|
||||
|
@ -94,11 +101,13 @@ describe('test data persistence', function() {
|
|||
deps.nedb = nedb;
|
||||
deps.nodemailer = nodemailer;
|
||||
deps.session = session;
|
||||
deps.winston = winston;
|
||||
deps.ldapjs = ldap;
|
||||
|
||||
var j1 = request.jar();
|
||||
var j2 = request.jar();
|
||||
|
||||
return start_server(config, ldap_client, deps)
|
||||
return start_server(config, deps)
|
||||
.then(function(s) {
|
||||
server = s;
|
||||
return requests.login(j1);
|
||||
|
@ -116,7 +125,7 @@ describe('test data persistence', function() {
|
|||
return stop_server(server);
|
||||
})
|
||||
.then(function() {
|
||||
return start_server(config, ldap_client, deps)
|
||||
return start_server(config, deps)
|
||||
})
|
||||
.then(function(s) {
|
||||
server = s;
|
||||
|
@ -139,9 +148,9 @@ describe('test data persistence', function() {
|
|||
});
|
||||
});
|
||||
|
||||
function start_server(config, ldap_client, deps) {
|
||||
function start_server(config, deps) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var s = server.run(config, ldap_client, deps);
|
||||
var s = server.run(config, deps);
|
||||
resolve(s);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,185 +1,232 @@
|
|||
|
||||
var ldap = require('../../src/lib/ldap');
|
||||
var Ldap = require('../../src/lib/ldap');
|
||||
var sinon = require('sinon');
|
||||
var Promise = require('bluebird');
|
||||
var assert = require('assert');
|
||||
var ldapjs = require('ldapjs');
|
||||
var winston = require('winston');
|
||||
|
||||
|
||||
describe('test ldap validation', function() {
|
||||
var ldap_client;
|
||||
var ldap, ldapjs;
|
||||
var ldap_config;
|
||||
|
||||
beforeEach(function() {
|
||||
ldap_client = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub(),
|
||||
modify: sinon.stub(),
|
||||
Change: sinon.spy()
|
||||
on: sinon.stub()
|
||||
};
|
||||
|
||||
ldapjs = {
|
||||
Change: sinon.spy(),
|
||||
createClient: sinon.spy(function() {
|
||||
return ldap_client;
|
||||
})
|
||||
}
|
||||
ldap_config = {
|
||||
url: 'http://localhost:324',
|
||||
user: 'admin',
|
||||
password: 'password',
|
||||
base_dn: 'dc=example,dc=com',
|
||||
additional_user_dn: 'ou=users'
|
||||
};
|
||||
|
||||
var deps = {};
|
||||
deps.ldapjs = ldapjs;
|
||||
deps.winston = winston;
|
||||
|
||||
ldap = new Ldap(deps, ldap_config);
|
||||
return ldap.connect();
|
||||
});
|
||||
|
||||
describe('test binding', test_binding);
|
||||
describe('test get email', test_get_email);
|
||||
describe('test get emails from username', test_get_emails);
|
||||
describe('test get groups from username', test_get_groups);
|
||||
describe('test update password', test_update_password);
|
||||
|
||||
function test_binding() {
|
||||
function test_validate() {
|
||||
var username = 'user';
|
||||
var password = 'password';
|
||||
var users_dn = 'dc=example,dc=com';
|
||||
return ldap.validate(ldap_client, username, password, users_dn);
|
||||
function test_bind() {
|
||||
var username = "username";
|
||||
var password = "password";
|
||||
return ldap.bind(username, password);
|
||||
}
|
||||
|
||||
it('should bind the user if good credentials provided', function() {
|
||||
ldap_client.bind.yields();
|
||||
return test_validate();
|
||||
return test_bind();
|
||||
});
|
||||
|
||||
it('should bind the user with correct DN', function(done) {
|
||||
it('should bind the user with correct DN', function() {
|
||||
ldap_config.user_name_attribute = 'uid';
|
||||
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);
|
||||
ldap_client.bind.withArgs('uid=user,ou=users,dc=example,dc=com').yields();
|
||||
return ldap.bind(username, password);
|
||||
});
|
||||
|
||||
it('should default to cn user search filter if no filter provided', function(done) {
|
||||
it('should default to cn user search filter if no filter provided', function() {
|
||||
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() {
|
||||
this.test = 'abc';
|
||||
}
|
||||
LdapClient.prototype.bind = function(username, password, fn) {
|
||||
assert.equal('abc', this.test);
|
||||
fn();
|
||||
}
|
||||
ldap_client = new LdapClient();
|
||||
return test_validate();
|
||||
ldap_client.bind.withArgs('cn=user,ou=users,dc=example,dc=com').yields();
|
||||
return ldap.bind(username, password);
|
||||
});
|
||||
|
||||
it('should not bind the user if wrong credentials provided', function() {
|
||||
ldap_client.bind.yields('wrong credentials');
|
||||
var promise = test_validate();
|
||||
var promise = test_bind();
|
||||
return promise.catch(function() {
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_get_email() {
|
||||
it('should retrieve the email of an existing user', function() {
|
||||
var expected_doc = {};
|
||||
function test_get_emails() {
|
||||
var res_emitter;
|
||||
var expected_doc;
|
||||
|
||||
beforeEach(function() {
|
||||
expected_doc = {};
|
||||
expected_doc.object = {};
|
||||
expected_doc.object.mail = 'user@example.com';
|
||||
var res_emitter = {};
|
||||
|
||||
res_emitter = {};
|
||||
res_emitter.on = sinon.spy(function(event, fn) {
|
||||
if(event != 'error') fn(expected_doc)
|
||||
});
|
||||
});
|
||||
|
||||
it('should retrieve the email of an existing user', function() {
|
||||
ldap_client.search.yields(undefined, res_emitter);
|
||||
|
||||
return ldap.get_email(ldap_client, 'user', 'dc=example,dc=com')
|
||||
.then(function(doc) {
|
||||
assert.deepEqual(doc, expected_doc.object);
|
||||
return ldap.get_emails('user')
|
||||
.then(function(emails) {
|
||||
assert.deepEqual(emails, [expected_doc.object.mail]);
|
||||
return Promise.resolve();
|
||||
})
|
||||
});
|
||||
|
||||
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();
|
||||
it('should retrieve email for user with uid name attribute', function() {
|
||||
ldap_config.user_name_attribute = 'uid';
|
||||
ldap_client.search.withArgs('uid=username,ou=users,dc=example,dc=com').yields(undefined, res_emitter);
|
||||
return ldap.get_emails('username')
|
||||
.then(function(emails) {
|
||||
assert.deepEqual(emails, ['user@example.com']);
|
||||
return Promise.resolve();
|
||||
});
|
||||
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() {
|
||||
var expected_doc = {};
|
||||
expected_doc.mail = [];
|
||||
expected_doc.mail.push('user@example.com');
|
||||
ldap_client.search.yields('error');
|
||||
|
||||
ldap.get_email(ldap_client, 'user', 'dc=example,dc=com')
|
||||
return ldap.get_emails('user')
|
||||
.catch(function() {
|
||||
return Promise.resolve();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function test_get_groups() {
|
||||
var res_emitter;
|
||||
var expected_doc1, expected_doc2;
|
||||
|
||||
beforeEach(function() {
|
||||
expected_doc1 = {};
|
||||
expected_doc1.object = {};
|
||||
expected_doc1.object.cn = 'group1';
|
||||
|
||||
expected_doc2 = {};
|
||||
expected_doc2.object = {};
|
||||
expected_doc2.object.cn = 'group2';
|
||||
|
||||
res_emitter = {};
|
||||
res_emitter.on = sinon.spy(function(event, fn) {
|
||||
if(event != 'error') fn(expected_doc1);
|
||||
if(event != 'error') fn(expected_doc2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should retrieve the groups of an existing user', function() {
|
||||
ldap_client.search.yields(undefined, res_emitter);
|
||||
return ldap.get_groups('user')
|
||||
.then(function(groups) {
|
||||
assert.deepEqual(groups, ['group1', 'group2']);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('should reduce the scope to additional_group_dn', function(done) {
|
||||
ldap_config.additional_group_dn = 'ou=groups';
|
||||
ldap_client.search = sinon.spy(function(base_dn) {
|
||||
assert.equal(base_dn, 'ou=groups,dc=example,dc=com');
|
||||
done();
|
||||
});
|
||||
ldap.get_groups('user');
|
||||
});
|
||||
|
||||
it('should use default group_name_attr if not provided', function(done) {
|
||||
ldap_client.search = sinon.spy(function(base_dn, query) {
|
||||
assert.equal(base_dn, 'dc=example,dc=com');
|
||||
assert.equal(query.filter, 'member=cn=user,ou=users,dc=example,dc=com');
|
||||
assert.deepEqual(query.attributes, ['cn']);
|
||||
done();
|
||||
});
|
||||
ldap.get_groups('user');
|
||||
});
|
||||
|
||||
it('should fail on error with search method', function() {
|
||||
ldap_client.search.yields('error');
|
||||
return ldap.get_groups('user')
|
||||
.catch(function() {
|
||||
return Promise.resolve();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function test_update_password() {
|
||||
it('should update the password successfully', function(done) {
|
||||
it('should update the password successfully', function() {
|
||||
var change = {};
|
||||
change.operation = 'replace';
|
||||
change.modification = {};
|
||||
change.modification.userPassword = 'new-password';
|
||||
|
||||
var config = {};
|
||||
config.ldap_user_search_base = 'dc=example,dc=com';
|
||||
config.ldap_user = 'admin';
|
||||
|
||||
var userdn = 'cn=user,dc=example,dc=com';
|
||||
|
||||
var ldapjs = {};
|
||||
ldapjs.Change = sinon.spy();
|
||||
var userdn = 'cn=user,ou=users,dc=example,dc=com';
|
||||
|
||||
ldap_client.bind.yields(undefined);
|
||||
ldap_client.modify.yields(undefined);
|
||||
|
||||
ldap.update_password(ldap_client, ldapjs, 'user', 'new-password', config)
|
||||
return ldap.update_password('user', 'new-password')
|
||||
.then(function() {
|
||||
assert.deepEqual(ldap_client.modify.getCall(0).args[0], userdn);
|
||||
assert.deepEqual(ldapjs.Change.getCall(0).args[0].operation, change.operation);
|
||||
|
||||
var userPassword = ldapjs.Change.getCall(0).args[0].modification.userPassword;
|
||||
assert(/{SSHA}/.test(userPassword));
|
||||
done();
|
||||
return Promise.resolve();
|
||||
})
|
||||
});
|
||||
|
||||
it('should fail when ldap throws an error', function(done) {
|
||||
it('should fail when ldap throws an error', function() {
|
||||
ldap_client.bind.yields(undefined);
|
||||
ldap_client.modify.yields('Error');
|
||||
|
||||
var config = {};
|
||||
config.ldap_users_dn = 'dc=example,dc=com';
|
||||
config.ldap_user = 'admin';
|
||||
|
||||
var ldapjs = {};
|
||||
ldapjs.Change = sinon.spy();
|
||||
|
||||
ldap.update_password(ldap_client, ldapjs, 'user', 'new-password', config)
|
||||
return ldap.update_password('user', 'new-password')
|
||||
.catch(function() {
|
||||
done();
|
||||
return Promise.resolve();
|
||||
})
|
||||
});
|
||||
|
||||
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';
|
||||
it('should update password of user using particular user name attribute', function() {
|
||||
ldap_config.user_name_attribute = 'uid';
|
||||
|
||||
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)
|
||||
ldap_client.modify.withArgs('uid=username,ou=users,dc=example,dc=com').yields();
|
||||
return ldap.update_password('username', 'newpass');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
var server = require('../../src/lib/server');
|
||||
var Ldap = require('../../src/lib/ldap');
|
||||
|
||||
var Promise = require('bluebird');
|
||||
var request = Promise.promisifyAll(require('request'));
|
||||
|
@ -8,6 +9,9 @@ var speakeasy = require('speakeasy');
|
|||
var sinon = require('sinon');
|
||||
var MockDate = require('mockdate');
|
||||
var session = require('express-session');
|
||||
var winston = require('winston');
|
||||
var speakeasy = require('speakeasy');
|
||||
var ldapjs = require('ldapjs');
|
||||
|
||||
var PORT = 8090;
|
||||
var BASE_URL = 'http://localhost:' + PORT;
|
||||
|
@ -19,14 +23,6 @@ describe('test the server', function() {
|
|||
var u2f, nedb;
|
||||
var transporter;
|
||||
var collection;
|
||||
var ldap_client = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub(),
|
||||
modify: sinon.stub(),
|
||||
};
|
||||
var ldap = {
|
||||
Change: sinon.spy()
|
||||
}
|
||||
|
||||
beforeEach(function(done) {
|
||||
var config = {
|
||||
|
@ -34,8 +30,8 @@ describe('test the server', function() {
|
|||
totp_secret: 'totp_secret',
|
||||
ldap: {
|
||||
url: 'ldap://127.0.0.1:389',
|
||||
user_search_base: 'ou=users,dc=example,dc=com',
|
||||
user_search_filter: 'cn',
|
||||
base_dn: 'ou=users,dc=example,dc=com',
|
||||
user_name_attribute: 'cn',
|
||||
user: 'cn=admin,dc=example,dc=com',
|
||||
password: 'password',
|
||||
},
|
||||
|
@ -52,6 +48,19 @@ describe('test the server', function() {
|
|||
}
|
||||
};
|
||||
|
||||
var ldap_client = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub(),
|
||||
modify: sinon.stub(),
|
||||
on: sinon.spy()
|
||||
};
|
||||
var ldap = {
|
||||
Change: sinon.spy(),
|
||||
createClient: sinon.spy(function() {
|
||||
return ldap_client;
|
||||
})
|
||||
};
|
||||
|
||||
u2f = {};
|
||||
u2f.startRegistration = sinon.stub();
|
||||
u2f.finishRegistration = sinon.stub();
|
||||
|
@ -68,15 +77,15 @@ describe('test the server', function() {
|
|||
return transporter;
|
||||
});
|
||||
|
||||
var search_doc = {
|
||||
ldap_document = {
|
||||
object: {
|
||||
mail: 'test_ok@example.com'
|
||||
mail: 'test_ok@example.com',
|
||||
}
|
||||
};
|
||||
|
||||
var search_res = {};
|
||||
search_res.on = sinon.spy(function(event, fn) {
|
||||
if(event != 'error') fn(search_doc);
|
||||
if(event != 'error') fn(ldap_document);
|
||||
});
|
||||
|
||||
ldap_client.bind.withArgs('cn=test_ok,ou=users,dc=example,dc=com',
|
||||
|
@ -94,10 +103,12 @@ describe('test the server', function() {
|
|||
deps.u2f = u2f;
|
||||
deps.nedb = nedb;
|
||||
deps.nodemailer = nodemailer;
|
||||
deps.ldap = ldap;
|
||||
deps.ldapjs = ldap;
|
||||
deps.session = session;
|
||||
deps.winston = winston;
|
||||
deps.speakeasy = speakeasy;
|
||||
|
||||
_server = server.run(config, ldap_client, deps, function() {
|
||||
_server = server.run(config, deps, function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -352,7 +363,6 @@ describe('test the server', function() {
|
|||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
console.log('coucou');
|
||||
assert.equal(res.statusCode, 401, 'first factor failed');
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
|
|
|
@ -26,7 +26,12 @@ describe('test server configuration', function() {
|
|||
|
||||
deps = {};
|
||||
deps.nedb = require('nedb');
|
||||
deps.winston = sinon.spy();
|
||||
deps.nodemailer = nodemailer;
|
||||
deps.ldapjs = {};
|
||||
deps.ldapjs.createClient = sinon.spy(function() {
|
||||
return { on: sinon.spy() };
|
||||
});
|
||||
deps.session = sinon.spy(function() {
|
||||
return function(req, res, next) { next(); };
|
||||
});
|
||||
|
@ -36,7 +41,9 @@ describe('test server configuration', function() {
|
|||
it('should set cookie scope to domain set in the config', function() {
|
||||
config.session = {};
|
||||
config.session.domain = 'example.com';
|
||||
server.run(config, undefined, deps);
|
||||
config.ldap = {};
|
||||
config.ldap.url = 'http://ldap';
|
||||
server.run(config, deps);
|
||||
|
||||
assert(deps.session.calledOnce);
|
||||
assert.equal(deps.session.getCall(0).args[0].cookie.domain, 'example.com');
|
||||
|
|
Loading…
Reference in New Issue