Merge pull request #21 from clems4ever/access-control

Adding access control in config to allow per-user and per-group subdomain restrictions
This commit is contained in:
Clément Michaud 2017-03-25 19:23:43 +01:00 committed by GitHub
commit 1910ad520d
24 changed files with 1112 additions and 311 deletions

View File

@ -40,6 +40,9 @@ Add the following lines to your /etc/hosts to simulate multiple subdomains
127.0.0.1 secret.test.local 127.0.0.1 secret.test.local
127.0.0.1 secret1.test.local 127.0.0.1 secret1.test.local
127.0.0.1 secret2.test.local 127.0.0.1 secret2.test.local
127.0.0.1 home.test.local
127.0.0.1 mx1.mail.test.local
127.0.0.1 mx2.mail.test.local
127.0.0.1 auth.test.local 127.0.0.1 auth.test.local
Then, type the following command to build and deploy the services: Then, type the following command to build and deploy the services:
@ -48,15 +51,28 @@ Then, type the following command to build and deploy the services:
docker-compose up -d docker-compose up -d
After few seconds the services should be running and you should be able to visit After few seconds the services should be running and you should be able to visit
[https://secret.test.local:8080/](https://secret.test.local:8080/). [https://home.test.local:8080/](https://home.test.local:8080/).
Normally, a self-signed certificate exception should appear, it has to be Normally, a self-signed certificate exception should appear, it has to be
accepted before getting to the login page: accepted before getting to the login page:
![first-factor-page](https://raw.githubusercontent.com/clems4ever/authelia/master/images/first_factor.png) ![first-factor-page](https://raw.githubusercontent.com/clems4ever/authelia/master/images/first_factor.png)
### 1st factor: LDAP ### 1st factor: LDAP and ACL
An LDAP server has been deployed for you with the following credentials: **user/password**. An LDAP server has been deployed for you with the following credentials and
access control list:
- **john / password** is in the admin group and has access to the secret from
any subdomain.
- **bob / password** is in the dev group and has access to the secret from
- [secret.test.local](https://secret.test.local:8080/secret.html)
- [secret2.test.local](https://secret2.test.local:8080/secret.html)
- [home.test.local](https://home.test.local:8080/secret.html)
- [\*.mail.test.local](https://mx1.mail.test.local:8080/secret.html)
- **harry / password** is not in a group but has rules giving him has access to
the secret from
- [secret1.test.local](https://secret1.test.local:8080/secret.html)
- [home.test.local](https://home.test.local:8080/secret.html)
Type them in the login page and validate. Then, the second factor page should Type them in the login page and validate. Then, the second factor page should
have appeared as shown below. have appeared as shown below.
@ -99,6 +115,12 @@ email address. For the sake of the example, the email is delivered in the file
./notifications/notification.txt. ./notifications/notification.txt.
Paste the link in your browser and you should be able to reset the password. Paste the link in your browser and you should be able to reset the password.
### Access Control
With **Authelia**, you can define your own access control rules for restricting
the access to certain subdomains to your users. Those rules are defined in the
configuration file and can be either default, per-user or per-group policies.
Check out the *config.template.yml* to see how they are defined.
## Documentation ## Documentation
### Configuration ### Configuration
The configuration of the server is defined in the file The configuration of the server is defined in the file

View File

@ -1,17 +1,67 @@
# The port to listen on
port: 80
# Log level
#
# Level of verbosity for logs # Level of verbosity for logs
logs_level: info logs_level: info
# Configuration of LDAP # LDAP configuration
#
# Example: for user john, the DN will be cn=john,ou=users,dc=example,dc=com # Example: for user john, the DN will be cn=john,ou=users,dc=example,dc=com
ldap: ldap:
# The url of the ldap server
url: ldap://ldap 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 user: cn=admin,dc=example,dc=com
password: password password: password
# Access Control
#
# Access control is a set of rules you can use to restrict the user access.
# Default (anyone), per-user or per-group rules can be defined.
#
# If 'access_control' is not defined, ACL rules are disabled and default policy
# is applied, i.e., access is allowed to anyone. Otherwise restrictions follow
# the rules defined below.
# If no rule is provided, all domains are denied.
#
# '*' means 'any' subdomains and matches any string. It must stand at the
# beginning of the pattern.
access_control:
default:
- home.test.local
groups:
admin:
- '*.test.local'
dev:
- secret.test.local
- secret2.test.local
users:
harry:
- secret1.test.local
bob:
- '*.mail.test.local'
# Configuration of session cookies # Configuration of session cookies
# #
# _secret_ the secret to encrypt session cookies # _secret_ the secret to encrypt session cookies

View File

@ -16,6 +16,9 @@ services:
- SLAPD_ORGANISATION=MyCompany - SLAPD_ORGANISATION=MyCompany
- SLAPD_DOMAIN=example.com - SLAPD_DOMAIN=example.com
- SLAPD_PASSWORD=password - SLAPD_PASSWORD=password
- SLAPD_ADDITIONAL_MODULES=memberof
- SLAPD_ADDITIONAL_SCHEMAS=openldap
- SLAPD_FORCE_RECONFIGURE=true
expose: expose:
- "389" - "389"
volumes: volumes:

View File

@ -8,39 +8,55 @@ objectclass: organizationalUnit
objectclass: top objectclass: top
ou: users ou: users
dn: cn=user,ou=groups,dc=example,dc=com dn: cn=dev,ou=groups,dc=example,dc=com
cn: user cn: dev
gidnumber: 502 member: cn=john,ou=users,dc=example,dc=com
objectclass: posixGroup member: cn=bob,ou=users,dc=example,dc=com
objectclass: groupOfNames
objectclass: top objectclass: top
dn: cn=user,ou=users,dc=example,dc=com dn: cn=admin,ou=groups,dc=example,dc=com
cn: user cn: admin
gidnumber: 500 member: cn=john,ou=users,dc=example,dc=com
givenname: user objectclass: groupOfNames
homedirectory: /home/user1
loginshell: /bin/sh
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: top objectclass: top
mail: user@example.com
sn: User dn: cn=john,ou=users,dc=example,dc=com
uid: user cn: john
uidnumber: 1000 objectclass: inetOrgPerson
objectclass: top
mail: john.doe@example.com
sn: John Doe
userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g= userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
dn: uid=useruid,ou=users,dc=example,dc=com dn: cn=harry,ou=users,dc=example,dc=com
cn: useruid cn: harry
gidnumber: 500
givenname: user
homedirectory: /home/user1
loginshell: /bin/sh
objectclass: inetOrgPerson objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: top objectclass: top
mail: useruid@example.com mail: harry.potter@example.com
sn: User sn: Harry Potter
uid: useruid
uidnumber: 1001
userpassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g= 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=
#

View File

@ -3,8 +3,81 @@
<title>Home page</title> <title>Home page</title>
</head> </head>
<body> <body>
You need to <a href="https://auth.test.local:8080/authentication/login?redirect=https://secret.test.local:8080/">log in</a> to access the <a href="/secret.html">secret</a>!<br/><br/> <h1>Access the secret</h1>
But you can also access it from another <a href="https://secret1.test.local:8080/secret.html">domain</a> or still <a href="https://secret2.test.local:8080/secret.html">another one</a>.<br/><br/> You need to log in to access the secret!<br/><br/>
You can also log off by visiting the following <a href="https://auth.test.local:8080/authentication/logout?redirect=https://secret.test.local:8080/">link</a>. Try to access it via one of the following links.<br/>
<ul>
<li>
<a href="https://secret.test.local:8080/secret.html">secret.test.local</a>
</li>
<li>
<a href="https://secret1.test.local:8080/secret.html">secret1.test.local</a>
</li>
<li>
<a href="https://secret2.test.local:8080/secret.html">secret2.test.local</a>
</li>
<li>
<a href="https://home.test.local:8080/secret.html">home.test.local</a>
</li>
<li>
<a href="https://mx1.mail.test.local:8080/secret.html">mx1.mail.test.local</a>
</li>
<li>
<a href="https://mx2.mail.test.local:8080/secret.html">mx2.mail.test.local</a>
</li>
</ul>
You can also log off by visiting the following <a href="https://auth.test.local:8080/authentication/logout?redirect=https://home.test.local:8080/">link</a>.
<h1>List of users</h1>
Here is the list of credentials you can log in with to test access control.
<ul>
<li><strong>john / password</strong>: belongs to <em>admin</em> and <em>dev</em> groups.</li>
<li><strong>bob / password</strong>: belongs to <em>dev</em> group only.</li>
<li><strong>harry / password</strong>: does not belong to any group.</li>
</ul>
<h1>Access control rules</h1>
<ul>
<li><strong>Default policy</strong>
<ul>
<li>home.test.local</li>
</ul>
</li>
<li><strong>Groups policy</strong>
<ul>
<li>admin
<ul>
<li>*.test.local</li>
</ul>
</li>
<li>dev
<ul>
<li>secret.test.local</li>
<li>secret2.test.local</li>
</ul>
</li>
</ul>
</li>
<li><strong>Users policy</strong>
<ul>
<li>harry
<ul>
<li>secret1.test.local</li>
</ul>
</li>
<li>bob
<ul>
<li>*.mail.test.local</li>
</ul>
</li>
</ul>
</li>
</ul>
</body> </body>
</html> </html>

View File

@ -60,7 +60,9 @@ http {
listen 443 ssl; listen 443 ssl;
root /usr/share/nginx/html; root /usr/share/nginx/html;
server_name secret1.test.local secret2.test.local secret.test.local localhost; server_name secret1.test.local secret2.test.local secret.test.local
home.test.local mx1.mail.test.local mx2.mail.test.local
localhost;
ssl on; ssl on;
ssl_certificate /etc/ssl/server.crt; ssl_certificate /etc/ssl/server.crt;
@ -73,8 +75,8 @@ http {
location /authentication/verify { location /authentication/verify {
proxy_set_header X-Original-URI $request_uri; 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 X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://auth/authentication/verify; proxy_pass http://auth/authentication/verify;
} }

View File

@ -3,6 +3,7 @@
<title>Secret</title> <title>Secret</title>
</head> </head>
<body> <body>
This is a very important secret! This is a very important secret!<br/>
Go back to <a href="https://home.test.local:8080/">home page</a>.
</body> </body>
</html> </html>

View File

@ -4,12 +4,14 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var server = require('./lib/server'); var server = require('./lib/server');
var ldap = require('ldapjs'); var ldapjs = require('ldapjs');
var u2f = require('authdog'); var u2f = require('authdog');
var nodemailer = require('nodemailer'); var nodemailer = require('nodemailer');
var nedb = require('nedb'); var nedb = require('nedb');
var YAML = require('yamljs'); var YAML = require('yamljs');
var session = require('express-session'); var session = require('express-session');
var winston = require('winston');
var speakeasy = require('speakeasy');
var config_path = process.argv[2]; var config_path = process.argv[2];
if(!config_path) { if(!config_path) {
@ -22,35 +24,13 @@ console.log('Parse configuration file: %s', config_path);
var yaml_config = YAML.load(config_path); 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_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,
session_secret: yaml_config.session.secret,
session_max_age: yaml_config.session.expiration || 3600000, // in ms
store_directory: yaml_config.store_directory,
logs_level: yaml_config.logs_level,
notifier: yaml_config.notifier,
}
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 = {}; var deps = {};
deps.u2f = u2f; deps.u2f = u2f;
deps.nedb = nedb; deps.nedb = nedb;
deps.nodemailer = nodemailer; deps.nodemailer = nodemailer;
deps.ldap = ldap; deps.ldapjs = ldapjs;
deps.session = session; deps.session = session;
deps.winston = winston;
deps.speakeasy = speakeasy;
server.run(config, ldap_client, deps); server.run(yaml_config, deps);

84
src/lib/access_control.js Normal file
View File

@ -0,0 +1,84 @@
module.exports = function(logger, acl_config) {
return {
builder: new AccessControlBuilder(logger, acl_config),
matcher: new AccessControlMatcher(logger)
};
}
var objectPath = require('object-path');
// *************** PER DOMAIN MATCHER ***************
function AccessControlMatcher(logger) {
this.logger = logger;
}
AccessControlMatcher.prototype.is_domain_allowed = function(domain, allowed_domains) {
// Allow all matcher
if(allowed_domains.length == 1 && allowed_domains[0] == '*') return true;
this.logger.debug('ACL: trying to match %s with %s', domain,
JSON.stringify(allowed_domains));
for(var i = 0; i < allowed_domains.length; ++i) {
var allowed_domain = allowed_domains[i];
if(allowed_domain.startsWith('*') &&
domain.endsWith(allowed_domain.substr(1))) {
return true;
}
else if(domain == allowed_domain) {
return true;
}
}
return false;
}
// *************** MATCHER BUILDER ***************
function AccessControlBuilder(logger, acl_config) {
this.logger = logger;
this.config = acl_config;
}
AccessControlBuilder.prototype.extract_per_group = function(groups) {
var allowed_domains = [];
var groups_policy = objectPath.get(this.config, 'groups');
if(groups_policy) {
for(var i=0; i<groups.length; ++i) {
var group = groups[i];
if(group in groups_policy) {
allowed_domains = allowed_domains.concat(groups_policy[group]);
}
}
  }
return allowed_domains;
}
AccessControlBuilder.prototype.extract_per_user = function(user) {
var allowed_domains = [];
var users_policy = objectPath.get(this.config, 'users');
if(users_policy) {
if(user in users_policy) {
allowed_domains = allowed_domains.concat(users_policy[user]);
}
  }
return allowed_domains;
}
AccessControlBuilder.prototype.get_allowed_domains = function(user, groups) {
var allowed_domains = [];
var default_policy = objectPath.get(this.config, 'default');
if(default_policy) {
allowed_domains = allowed_domains.concat(default_policy);
}
allowed_domains = allowed_domains.concat(this.extract_per_group(groups));
allowed_domains = allowed_domains.concat(this.extract_per_user(user));
this.logger.debug('ACL: user \'%s\' is allowed access to %s', user,
JSON.stringify(allowed_domains));
return allowed_domains;
}
AccessControlBuilder.prototype.get_any_domain = function() {
return ['*'];
}

17
src/lib/config_adapter.js Normal file
View File

@ -0,0 +1,17 @@
var objectPath = require('object-path');
module.exports = function(yaml_config) {
return {
port: objectPath.get(yaml_config, 'port', 8080),
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')
}
};

View File

@ -1,46 +1,66 @@
module.exports = { module.exports = Ldap;
validate: validate_credentials,
get_email: retrieve_email,
update_password: update_password
}
var util = require('util'); var util = require('util');
var Promise = require('bluebird'); var Promise = require('bluebird');
var exceptions = require('./exceptions'); var exceptions = require('./exceptions');
var Dovehash = require('dovehash'); var Dovehash = require('dovehash');
function validate_credentials(ldap_client, username, password, user_base, user_filter) { function Ldap(deps, ldap_config) {
// if not provided, default to cn this.ldap_config = ldap_config;
if(!user_filter) user_filter = 'cn';
var userDN = util.format("%s=%s,%s", user_filter, username, user_base); this.ldapjs = deps.ldapjs;
console.log(userDN); this.logger = deps.winston;
var bind_promised = Promise.promisify(ldap_client.bind, { context: ldap_client });
return bind_promised(userDN, password) 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) { .error(function(err) {
console.error(err);
throw new exceptions.LdapBindError(err.message); throw new exceptions.LdapBindError(err.message);
}); });
} }
function retrieve_email(ldap_client, username, user_base, user_filter) { Ldap.prototype._search_in_ldap = function(base, query) {
// if not provided, default to cn var that = this;
if(!user_filter) user_filter = 'cn'; this.logger.debug('LDAP: Search for %s in %s', JSON.stringify(query), base);
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'];
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
search_promised(userDN, query) that.ldap_client.searchAsync(base, query)
.then(function(res) { .then(function(res) {
var doc; var doc = [];
res.on('searchEntry', function(entry) { res.on('searchEntry', function(entry) {
doc = entry.object; doc.push(entry.object);
}); });
res.on('error', function(err) { res.on('error', function(err) {
reject(new exceptions.LdapSearchError(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) { Ldap.prototype.get_groups = function(username) {
var user_filter = config.ldap_user_search_filter; var user_dn = this._build_user_dn(username);
// if not provided, default to cn
if(!user_filter) user_filter = 'cn'; 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 encoded_password = Dovehash.encode('SSHA', new_password);
var change = new ldap.Change({ var change = new this.ldapjs.Change({
operation: 'replace', operation: 'replace',
modification: { modification: {
userPassword: encoded_password userPassword: encoded_password
} }
}); });
var modify_promised = Promise.promisify(ldap_client.modify, { context: ldap_client }); var that = this;
var bind_promised = Promise.promisify(ldap_client.bind, { context: ldap_client }); 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() { .then(function() {
return modify_promised(userDN, change); that.logger.debug('LDAP: modify password');
return that.ldap_client.modifyAsync(user_dn, change);
}); });
} }

View File

@ -2,11 +2,29 @@
module.exports = first_factor; module.exports = first_factor;
var exceptions = require('../exceptions'); var exceptions = require('../exceptions');
var ldap = require('../ldap');
var objectPath = require('object-path'); var objectPath = require('object-path');
var Promise = require('bluebird');
function get_allowed_domains(access_control, username, groups) {
var allowed_domains = [];
for(var i = 0; i<access_control.length; ++i) {
var rule = access_control[i];
if('allowed_domains' in rule) {
if('group' in rule && groups.indexOf(rule['group']) >= 0) {
var domains = rule.allowed_domains;
allowed_domains = allowed_domains.concat(domains);
}
else if('user' in rule && username == rule['user']) {
var domains = rule.allowed_domains;
allowed_domains = allowed_domains.concat(domains);
}
}
}
return allowed_domains;
}
function first_factor(req, res) { function first_factor(req, res) {
var logger = req.app.get('logger');
var username = req.body.username; var username = req.body.username;
var password = req.body.password; var password = req.body.password;
if(!username || !password) { if(!username || !password) {
@ -15,59 +33,70 @@ function first_factor(req, res) {
return; return;
} }
logger.info('1st factor: Starting authentication of user "%s"', username); var logger = req.app.get('logger');
var ldap = req.app.get('ldap');
var ldap_client = req.app.get('ldap client');
var config = req.app.get('config'); var config = req.app.get('config');
var regulator = req.app.get('authentication regulator'); var regulator = req.app.get('authentication regulator');
var acl_builder = req.app.get('access control').builder;
logger.info('1st factor: Starting authentication of user "%s"', username);
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_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_user_search_base, config.ldap_user_search_filter); return ldap.bind(username, password);
}) })
.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_user_search_base, return Promise.join(ldap.get_emails(username), ldap.get_groups(username));
config.ldap_user_search_filter)
}) })
.then(function(doc) { .then(function(data) {
var email = objectPath.get(doc, 'mail'); var emails = data[0];
logger.debug('1st factor: document=%s', JSON.stringify(doc)); var groups = data[1];
logger.debug('1st factor: Retrieved email is %s', email); var allowed_domains;
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) {
allowed_domains = acl_builder.get_allowed_domains(username, groups);
}
else {
allowed_domains = acl_builder.get_any_domain();
logger.debug('1st factor: no access control rules found.' +
'Default policy to allow all.');
}
objectPath.set(req, 'session.auth_session.allowed_domains', allowed_domains);
objectPath.set(req, 'session.auth_session.email', email);
regulator.mark(username, true); regulator.mark(username, true);
res.status(204); res.status(204);
res.send(); res.send();
}) })
.catch(exceptions.LdapSearchError, function(err) { .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.status(500);
res.send(); res.send();
}) })
.catch(exceptions.LdapBindError, function(err) { .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); logger.debug('1st factor: LDAP binding failed due to ', err);
regulator.mark(username, false); regulator.mark(username, false);
res.status(401); res.status(401);
res.send('Bad credentials'); res.send('Bad credentials');
}) })
.catch(exceptions.AuthenticationRegulationError, function(err) { .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); logger.debug('1st factor: authentication rejected due to %s', err);
res.status(403); res.status(403);
res.send('Access has been restricted for a few minutes...'); res.send('Access has been restricted for a few minutes...');
}) })
.catch(function(err) { .catch(function(err) {
logger.debug('1st factor: Unhandled error %s', err); logger.error('1st factor: Unhandled error %s', err);
res.status(500); res.status(500);
res.send('Internal error'); res.send('Internal error');
}); });

View File

@ -1,7 +1,6 @@
var Promise = require('bluebird'); var Promise = require('bluebird');
var objectPath = require('object-path'); var objectPath = require('object-path');
var ldap = require('../ldap');
var exceptions = require('../exceptions'); var exceptions = require('../exceptions');
var CHALLENGE = 'reset-password'; var CHALLENGE = 'reset-password';
@ -24,16 +23,14 @@ function pre_check(req) {
return Promise.reject(err); return Promise.reject(err);
} }
var ldap_client = req.app.get('ldap client'); var ldap = req.app.get('ldap');
var config = req.app.get('config');
return ldap.get_email(ldap_client, userid, config.ldap_user_search_base, return ldap.get_emails(userid)
config.ldap_user_search_filter) .then(function(emails) {
.then(function(doc) { if(!emails && emails.length <= 0) throw new Error('No email found');
var email = objectPath.get(doc, 'mail');
var identity = {} var identity = {}
identity.email = email; identity.email = emails[0];
identity.userid = userid; identity.userid = userid;
return Promise.resolve(identity); return Promise.resolve(identity);
}); });
@ -53,15 +50,13 @@ function protect(fn) {
function post(req, res) { function post(req, res) {
var logger = req.app.get('logger'); var logger = req.app.get('logger');
var ldapjs = req.app.get('ldap'); var ldap = req.app.get('ldap');
var ldap_client = req.app.get('ldap client');
var new_password = objectPath.get(req, 'body.password'); var new_password = objectPath.get(req, 'body.password');
var userid = objectPath.get(req, 'session.auth_session.identity_check.userid'); 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); 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() { .then(function() {
logger.info('POST reset-password: Password reset for user %s', userid); logger.info('POST reset-password: Password reset for user %s', userid);
objectPath.set(req, 'session.auth_session', undefined); objectPath.set(req, 'session.auth_session', undefined);

View File

@ -5,6 +5,8 @@ var objectPath = require('object-path');
var Promise = require('bluebird'); var Promise = require('bluebird');
function verify_filter(req, res) { function verify_filter(req, res) {
var logger = req.app.get('logger');
if(!objectPath.has(req, 'session.auth_session')) if(!objectPath.has(req, 'session.auth_session'))
return Promise.reject('No auth_session variable'); return Promise.reject('No auth_session variable');
@ -14,6 +16,21 @@ function verify_filter(req, res) {
if(!objectPath.has(req, 'session.auth_session.second_factor')) if(!objectPath.has(req, 'session.auth_session.second_factor'))
return Promise.reject('No second factor variable'); return Promise.reject('No second factor variable');
if(!objectPath.has(req, 'session.auth_session.userid'))
return Promise.reject('No userid variable');
if(!objectPath.has(req, 'session.auth_session.allowed_domains'))
return Promise.reject('No allowed_domains variable');
// Get the session ACL matcher
var allowed_domains = objectPath.get(req, 'session.auth_session.allowed_domains');
var host = objectPath.get(req, 'headers.host');
var domain = host.split(':')[0];
var acl_matcher = req.app.get('access control').matcher;
if(!acl_matcher.is_domain_allowed(domain, allowed_domains))
return Promise.reject('Access restricted by ACL rules');
if(!req.session.auth_session.first_factor || if(!req.session.auth_session.first_factor ||
!req.session.auth_session.second_factor) !req.session.auth_session.second_factor)
return Promise.reject('First or second factor not validated'); return Promise.reject('First or second factor not validated');
@ -28,6 +45,7 @@ function verify(req, res) {
res.send(); res.send();
}) })
.catch(function(err) { .catch(function(err) {
req.app.get('logger').error(err);
res.status(401); res.status(401);
res.send(); res.send();
}); });

View File

@ -5,15 +5,18 @@ module.exports = {
var express = require('express'); var express = require('express');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var speakeasy = require('speakeasy');
var path = require('path'); var path = require('path');
var winston = require('winston');
var UserDataStore = require('./user_data_store'); var UserDataStore = require('./user_data_store');
var Notifier = require('./notifier'); var Notifier = require('./notifier');
var AuthenticationRegulator = require('./authentication_regulator'); var AuthenticationRegulator = require('./authentication_regulator');
var setup_endpoints = require('./setup_endpoints'); var setup_endpoints = require('./setup_endpoints');
var config_adapter = require('./config_adapter');
var Ldap = require('./ldap');
var AccessControl = require('./access_control');
function run(yaml_config, deps, fn) {
var config = config_adapter(yaml_config);
function run(config, ldap_client, deps, fn) {
var view_directory = path.resolve(__dirname, '../views'); var view_directory = path.resolve(__dirname, '../views');
var public_html_directory = path.resolve(__dirname, '../public_html'); var public_html_directory = path.resolve(__dirname, '../public_html');
var datastore_options = {}; var datastore_options = {};
@ -42,22 +45,24 @@ function run(config, ldap_client, deps, fn) {
app.set('view engine', 'ejs'); app.set('view engine', 'ejs');
// by default the level of logs is info // 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 five_minutes = 5 * 60;
var data_store = new UserDataStore(deps.nedb, datastore_options); var data_store = new UserDataStore(deps.nedb, datastore_options);
var regulator = new AuthenticationRegulator(data_store, five_minutes); var regulator = new AuthenticationRegulator(data_store, five_minutes);
var notifier = new Notifier(config.notifier, deps); var notifier = new Notifier(config.notifier, deps);
var ldap = new Ldap(deps, config.ldap);
var access_control = AccessControl(deps.winston, config.access_control);
app.set('logger', winston); app.set('logger', deps.winston);
app.set('ldap', deps.ldap); app.set('ldap', ldap);
app.set('ldap client', ldap_client); app.set('totp engine', deps.speakeasy);
app.set('totp engine', speakeasy);
app.set('u2f', deps.u2f); app.set('u2f', deps.u2f);
app.set('user data store', data_store); app.set('user data store', data_store);
app.set('notifier', notifier); app.set('notifier', notifier);
app.set('authentication regulator', regulator); app.set('authentication regulator', regulator);
app.set('config', config); app.set('config', config);
app.set('access control', access_control);
setup_endpoints(app); setup_endpoints(app);

View File

@ -5,35 +5,30 @@ var assert = require('assert');
var winston = require('winston'); var winston = require('winston');
var first_factor = require('../../../src/lib/routes/first_factor'); var first_factor = require('../../../src/lib/routes/first_factor');
var exceptions = require('../../../src/lib/exceptions'); var exceptions = require('../../../src/lib/exceptions');
var Ldap = require('../../../src/lib/ldap');
var AccessControl = require('../../../src/lib/access_control');
describe('test the first factor validation route', function() { describe('test the first factor validation route', function() {
var req, res; var req, res;
var ldap_interface_mock; var ldap_interface_mock;
var emails;
var search_res_ok; var search_res_ok;
var regulator; var regulator;
var access_control;
var config;
beforeEach(function() { beforeEach(function() {
ldap_interface_mock = { ldap_interface_mock = sinon.createStubInstance(Ldap);
bind: sinon.stub(), config = {
search: sinon.stub() ldap: {
} base_dn: 'ou=users,dc=example,dc=com',
var config = { user_name_attribute: 'uid'
ldap_user_search_base: 'ou=users,dc=example,dc=com',
ldap_user_search_filter: 'uid'
}
var search_doc = {
object: {
mail: 'test_ok@example.com'
} }
}; }
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 = {};
regulator.mark = sinon.stub(); regulator.mark = sinon.stub();
regulator.regulate = sinon.stub(); regulator.regulate = sinon.stub();
@ -41,11 +36,22 @@ describe('test the first factor validation route', function() {
regulator.mark.returns(Promise.resolve()); regulator.mark.returns(Promise.resolve());
regulator.regulate.returns(Promise.resolve()); regulator.regulate.returns(Promise.resolve());
access_control = {
builder: {
get_allowed_domains: sinon.stub(),
get_any_domain: sinon.stub(),
},
matcher: {
is_domain_allowed: sinon.stub()
}
};
var app_get = sinon.stub(); 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('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);
app_get.withArgs('access control').returns(access_control);
req = { req = {
app: { app: {
@ -75,43 +81,83 @@ describe('test the first factor validation route', function() {
assert.equal(204, res.status.getCall(0).args[0]); assert.equal(204, res.status.getCall(0).args[0]);
resolve(); 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); first_factor(req, res);
}); });
}); });
it('should bind user based on LDAP DN', function(done) { describe('store the ACL matcher in the auth session', function() {
ldap_interface_mock.bind = sinon.spy(function(dn) { it('should store the allowed domains in the auth session', function() {
if(dn == 'uid=username,ou=users,dc=example,dc=com') done(); config.access_control = {};
access_control.builder.get_allowed_domains.returns(['example.com', 'test.example.com']);
return new Promise(function(resolve, reject) {
res.send = sinon.spy(function(data) {
assert.deepEqual(['example.com', 'test.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);
});
});
it('should store the allow all ACL matcher in the auth session', function() {
access_control.builder.get_any_domain.returns(['*']);
return new Promise(function(resolve, reject) {
res.send = sinon.spy(function(data) {
assert(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) { it('should retrieve email from LDAP', function(done) {
ldap_interface_mock.bind.yields(undefined); res.send = sinon.spy(function(data) { done(); });
ldap_interface_mock.search = sinon.spy(function(dn) { ldap_interface_mock.bind.returns(Promise.resolve());
if(dn == 'uid=username,ou=users,dc=example,dc=com') done(); ldap_interface_mock.get_emails = sinon.stub().withArgs('usernam').returns(Promise.resolve([{mail: ['test@example.com'] }]));
});
first_factor(req, res); 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) { res.send = sinon.spy(function(data) {
assert.equal(401, res.status.getCall(0).args[0]); assert.equal(401, res.status.getCall(0).args[0]);
assert.equal(regulator.mark.getCall(0).args[0], 'username'); assert.equal(regulator.mark.getCall(0).args[0], 'username');
done(); done();
}); });
ldap_interface_mock.bind.yields('Bad credentials'); ldap_interface_mock.bind.throws(new exceptions.LdapBindError('Bad credentials'));
first_factor(req, res); 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) { res.send = sinon.spy(function(data) {
assert.equal(500, res.status.getCall(0).args[0]); assert.equal(500, res.status.getCall(0).args[0]);
done(); done();
}); });
ldap_interface_mock.bind.yields(undefined); ldap_interface_mock.bind.returns(Promise.resolve());
ldap_interface_mock.search.yields('error'); ldap_interface_mock.get_emails.throws(new exceptions.LdapSearchError('err'));
first_factor(req, res); first_factor(req, res);
}); });
@ -122,8 +168,8 @@ describe('test the first factor validation route', function() {
assert.equal(403, res.status.getCall(0).args[0]); assert.equal(403, res.status.getCall(0).args[0]);
done(); done();
}); });
ldap_interface_mock.bind.yields(undefined); ldap_interface_mock.bind.returns(Promise.resolve());
ldap_interface_mock.search.yields(undefined); ldap_interface_mock.get_emails.returns(Promise.resolve());
first_factor(req, res); first_factor(req, res);
}); });
}); });

View File

@ -1,6 +1,8 @@
var reset_password = require('../../../src/lib/routes/reset_password');
var Ldap = require('../../../src/lib/ldap');
var sinon = require('sinon'); var sinon = require('sinon');
var winston = require('winston'); var winston = require('winston');
var reset_password = require('../../../src/lib/routes/reset_password');
var assert = require('assert'); var assert = require('assert');
describe('test reset password', function() { 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({})); user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({}));
req.app.get.withArgs('user data store').returns(user_data_store); req.app.get.withArgs('user data store').returns(user_data_store);
ldap = {};
ldap.Change = sinon.spy(); config = {};
req.app.get.withArgs('ldap').returns(ldap); 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 = {};
ldap_client.bind = sinon.stub(); ldap_client.bind = sinon.stub();
ldap_client.search = sinon.stub(); ldap_client.search = sinon.stub();
ldap_client.modify = sinon.stub(); ldap_client.modify = sinon.stub();
req.app.get.withArgs('ldap client').returns(ldap_client); ldap_client.on = sinon.spy();
config = {}; ldapjs = {};
config.ldap_user_search_base = 'dc=example,dc=com'; ldapjs.Change = sinon.spy();
config.ldap_user_search_filter = 'cn'; ldapjs.createClient = sinon.spy(function() {
req.app.get.withArgs('config').returns(config); return ldap_client;
});
deps = {
ldapjs: ldapjs,
winston: winston
};
req.app.get.withArgs('ldap').returns(new Ldap(deps, config.ldap));
res = {}; res = {};
res.send = sinon.spy(); 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) { 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) { ldap_client.search = sinon.spy(function(dn) {
console.log(dn);
if(dn == 'uid=user,dc=example,dc=com') done(); if(dn == 'uid=user,dc=example,dc=com') done();
}); });
reset_password.icheck_interface.pre_check_callback(req); 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) { it('should returns identity when ldap replies', function(done) {
var doc = {}; var doc = {};
doc.object = {}; doc.object = {};
doc.object.email = 'test@example.com'; doc.object.email = ['test@example.com'];
doc.object.userid = 'user'; doc.object.userid = 'user';
var res = {}; var res = {};

View File

@ -2,19 +2,41 @@
var assert = require('assert'); var assert = require('assert');
var verify = require('../../../src/lib/routes/verify'); var verify = require('../../../src/lib/routes/verify');
var sinon = require('sinon'); var sinon = require('sinon');
var winston = require('winston');
describe('test authentication token verification', function() { describe('test authentication token verification', function() {
var req, res; var req, res;
var config_mock;
var acl_matcher;
beforeEach(function() { beforeEach(function() {
acl_matcher = {
is_domain_allowed: sinon.stub().returns(true)
};
var access_control = {
matcher: acl_matcher
}
config_mock = {};
req = {}; req = {};
res = {}; 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);
req.app.get.withArgs('access control').returns(access_control);
res.status = sinon.spy(); res.status = sinon.spy();
}); });
it('should be already authenticated', function(done) { it('should be already authenticated', function(done) {
req.session = {}; req.session = {};
req.session.auth_session = {first_factor: true, second_factor: true}; req.session.auth_session = {
first_factor: true,
second_factor: true,
userid: 'myuser',
allowed_domains: ['*']
};
res.send = sinon.spy(function() { res.send = sinon.spy(function() {
assert.equal(204, res.status.getCall(0).args[0]); assert.equal(204, res.status.getCall(0).args[0]);
@ -25,13 +47,13 @@ describe('test authentication token verification', function() {
}); });
describe('given different cases of session', 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) { return new Promise(function(resolve, reject) {
req.session = {}; req.session = {};
req.session.auth_session = auth_session; req.session.auth_session = auth_session;
res.send = sinon.spy(function() { 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(); resolve();
}); });
@ -39,6 +61,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() { it('should not be authenticated when second factor is missing', function() {
return test_unauthorized({ first_factor: true, second_factor: false }); return test_unauthorized({ first_factor: true, second_factor: false });
}); });
@ -47,6 +77,14 @@ describe('test authentication token verification', function() {
return test_unauthorized({ first_factor: false, second_factor: true }); 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() { it('should not be authenticated when first and second factor are missing', function() {
return test_unauthorized({ first_factor: false, second_factor: false }); return test_unauthorized({ first_factor: false, second_factor: false });
}); });
@ -55,6 +93,25 @@ describe('test authentication token verification', function() {
return test_unauthorized(undefined); return test_unauthorized(undefined);
}); });
it('should reply unauthorized when the domain is restricted', function() {
acl_matcher.is_domain_allowed.returns(false);
return test_unauthorized({
first_factor: true,
second_factor: true,
userid: 'user',
allowed_domains: []
});
});
it('should reply authorized when the domain is allowed', function() {
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() { it('should not be authenticated when session is partially initialized', function() {
return test_unauthorized({ first_factor: true }); return test_unauthorized({ first_factor: true });
}); });

View File

@ -0,0 +1,160 @@
var assert = require('assert');
var winston = require('winston');
var AccessControl = require('../../src/lib/access_control');
describe('test access control manager', function() {
var access_control;
var acl_config;
var acl_builder;
var acl_matcher;
beforeEach(function() {
acl_config = {};
access_control = AccessControl(winston, acl_config);
acl_builder = access_control.builder;
acl_matcher = access_control.matcher;
});
describe('building user group access control matcher', function() {
it('should deny all if nothing is defined in the config', function() {
var allowed_domains = acl_builder.get_allowed_domains('user', ['group1', 'group2']);
assert.deepEqual(allowed_domains, []);
});
it('should allow domain test.example.com to all users if defined in' +
' default policy', function() {
acl_config.default = ['test.example.com'];
var allowed_domains = acl_builder.get_allowed_domains('user', ['group1', 'group2']);
assert.deepEqual(allowed_domains, ['test.example.com']);
});
it('should allow domain test.example.com to all users in group mygroup', function() {
var allowed_domains0 = acl_builder.get_allowed_domains('user', ['group1', 'group1']);
assert.deepEqual(allowed_domains0, []);
acl_config.groups = {
mygroup: ['test.example.com']
};
var allowed_domains1 = acl_builder.get_allowed_domains('user', ['group1', 'group2']);
assert.deepEqual(allowed_domains1, []);
var allowed_domains2 = acl_builder.get_allowed_domains('user', ['group1', 'mygroup']);
assert.deepEqual(allowed_domains2, ['test.example.com']);
});
it('should allow domain test.example.com based on per user config', function() {
var allowed_domains0 = acl_builder.get_allowed_domains('user', ['group1']);
assert.deepEqual(allowed_domains0, []);
acl_config.users = {
user1: ['test.example.com']
};
var allowed_domains1 = acl_builder.get_allowed_domains('user', ['group1', 'mygroup']);
assert.deepEqual(allowed_domains1, []);
var allowed_domains2 = acl_builder.get_allowed_domains('user1', ['group1', 'mygroup']);
assert.deepEqual(allowed_domains2, ['test.example.com']);
});
it('should allow domains from user and groups', function() {
acl_config.groups = {
group2: ['secret.example.com', 'secret1.example.com']
};
acl_config.users = {
user: ['test.example.com']
};
var allowed_domains0 = acl_builder.get_allowed_domains('user', ['group1', 'group2']);
assert.deepEqual(allowed_domains0, [
'secret.example.com',
'secret1.example.com',
'test.example.com',
]);
});
it('should allow domains from several groups', function() {
acl_config.groups = {
group1: ['secret2.example.com'],
group2: ['secret.example.com', 'secret1.example.com']
};
var allowed_domains0 = acl_builder.get_allowed_domains('user', ['group1', 'group2']);
assert.deepEqual(allowed_domains0, [
'secret2.example.com',
'secret.example.com',
'secret1.example.com',
]);
});
it('should allow domains from several groups and default policy', function() {
acl_config.default = ['home.example.com'];
acl_config.groups = {
group1: ['secret2.example.com'],
group2: ['secret.example.com', 'secret1.example.com']
};
var allowed_domains0 = acl_builder.get_allowed_domains('user', ['group1', 'group2']);
assert.deepEqual(allowed_domains0, [
'home.example.com',
'secret2.example.com',
'secret.example.com',
'secret1.example.com',
]);
});
});
describe('building user group access control matcher', function() {
it('should allow access to any subdomain', function() {
var allowed_domains = acl_builder.get_any_domain();
assert(acl_matcher.is_domain_allowed('example.com', allowed_domains));
assert(acl_matcher.is_domain_allowed('mail.example.com', allowed_domains));
assert(acl_matcher.is_domain_allowed('test.example.com', allowed_domains));
assert(acl_matcher.is_domain_allowed('user.mail.example.com', allowed_domains));
assert(acl_matcher.is_domain_allowed('public.example.com', allowed_domains));
assert(acl_matcher.is_domain_allowed('example2.com', allowed_domains));
});
});
describe('check access control matching', function() {
beforeEach(function() {
acl_config.default = ['home.example.com', '*.public.example.com'];
acl_config.users = {
user1: ['user1.example.com', 'user1.mail.example.com']
};
acl_config.groups = {
group1: ['secret2.example.com'],
group2: ['secret.example.com', 'secret1.example.com']
};
});
it('should allow access to secret.example.com', function() {
var allowed_domains = acl_builder.get_allowed_domains('user', ['group1', 'group2']);
assert(acl_matcher.is_domain_allowed('secret.example.com', allowed_domains));
});
it('should deny access to secret3.example.com', function() {
var allowed_domains = acl_builder.get_allowed_domains('user', ['group1', 'group2']);
assert(!acl_matcher.is_domain_allowed('secret3.example.com', allowed_domains));
});
it('should allow access to home.example.com', function() {
var allowed_domains = acl_builder.get_allowed_domains('user', ['group1', 'group2']);
assert(acl_matcher.is_domain_allowed('home.example.com', allowed_domains));
});
it('should allow access to user1.example.com', function() {
var allowed_domains = acl_builder.get_allowed_domains('user1', ['group1', 'group2']);
assert(acl_matcher.is_domain_allowed('user1.example.com', allowed_domains));
});
it('should allow access *.public.example.com', function() {
var allowed_domains = acl_builder.get_allowed_domains('nouser', []);
assert(acl_matcher.is_domain_allowed('user.public.example.com', allowed_domains));
assert(acl_matcher.is_domain_allowed('test.public.example.com', allowed_domains));
});
});
});

View File

@ -0,0 +1,76 @@
var assert = require('assert');
var config_adapter = require('../../src/lib/config_adapter');
describe('test config adapter', function() {
it('should read the port from the yaml file', function() {
yaml_config = {};
yaml_config.port = 7070;
var config = config_adapter(yaml_config);
assert.equal(config.port, 7070);
});
it('should default the port to 8080 if not provided', function() {
yaml_config = {};
var config = config_adapter(yaml_config);
assert.equal(config.port, 8080);
});
it('should get the ldap attributes', function() {
yaml_config = {};
yaml_config.ldap = {};
yaml_config.ldap.url = 'http://ldap';
yaml_config.ldap.user_search_base = 'ou=groups,dc=example,dc=com';
yaml_config.ldap.user_search_filter = 'uid';
yaml_config.ldap.user = 'admin';
yaml_config.ldap.password = 'pass';
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');
});
it('should get the session attributes', function() {
yaml_config = {};
yaml_config.session = {};
yaml_config.session.domain = 'example.com';
yaml_config.session.secret = 'secret';
yaml_config.session.expiration = 3600;
var config = config_adapter(yaml_config);
assert.equal(config.session_domain, 'example.com');
assert.equal(config.session_secret, 'secret');
assert.equal(config.session_max_age, 3600);
});
it('should get the log level', function() {
yaml_config = {};
yaml_config.logs_level = 'debug';
var config = config_adapter(yaml_config);
assert.equal(config.logs_level, 'debug');
});
it('should get the notifier config', function() {
yaml_config = {};
yaml_config.notifier = 'notifier';
var config = config_adapter(yaml_config);
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');
});
});

View File

@ -9,6 +9,7 @@ var sinon = require('sinon');
var tmp = require('tmp'); var tmp = require('tmp');
var nedb = require('nedb'); var nedb = require('nedb');
var session = require('express-session'); var session = require('express-session');
var winston = require('winston');
var PORT = 8050; var PORT = 8050;
var BASE_URL = 'http://localhost:' + PORT; var BASE_URL = 'http://localhost:' + PORT;
@ -21,8 +22,14 @@ describe('test data persistence', function() {
var tmpDir; var tmpDir;
var ldap_client = { var ldap_client = {
bind: sinon.stub(), bind: sinon.stub(),
search: sinon.stub() search: sinon.stub(),
on: sinon.spy()
}; };
var ldap = {
createClient: sinon.spy(function() {
return ldap_client;
})
}
var config; var config;
before(function() { before(function() {
@ -53,10 +60,14 @@ describe('test data persistence', function() {
config = { config = {
port: PORT, port: PORT,
totp_secret: 'totp_secret', totp_secret: 'totp_secret',
ldap_url: 'ldap://127.0.0.1:389', ldap: {
ldap_user_search_base: 'ou=users,dc=example,dc=com', url: 'ldap://127.0.0.1:389',
session_secret: 'session_secret', base_dn: 'ou=users,dc=example,dc=com',
session_max_age: 50000, },
session: {
secret: 'session_secret',
expiration: 50000,
},
store_directory: tmpDir.name, store_directory: tmpDir.name,
notifier: { gmail: { user: 'user@example.com', pass: 'password' } } notifier: { gmail: { user: 'user@example.com', pass: 'password' } }
}; };
@ -90,11 +101,13 @@ describe('test data persistence', function() {
deps.nedb = nedb; deps.nedb = nedb;
deps.nodemailer = nodemailer; deps.nodemailer = nodemailer;
deps.session = session; deps.session = session;
deps.winston = winston;
deps.ldapjs = ldap;
var j1 = request.jar(); var j1 = request.jar();
var j2 = request.jar(); var j2 = request.jar();
return start_server(config, ldap_client, deps) return start_server(config, deps)
.then(function(s) { .then(function(s) {
server = s; server = s;
return requests.login(j1); return requests.login(j1);
@ -112,7 +125,7 @@ describe('test data persistence', function() {
return stop_server(server); return stop_server(server);
}) })
.then(function() { .then(function() {
return start_server(config, ldap_client, deps) return start_server(config, deps)
}) })
.then(function(s) { .then(function(s) {
server = s; server = s;
@ -135,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) { return new Promise(function(resolve, reject) {
var s = server.run(config, ldap_client, deps); var s = server.run(config, deps);
resolve(s); resolve(s);
}); });
} }

View File

@ -1,185 +1,232 @@
var ldap = require('../../src/lib/ldap'); var Ldap = require('../../src/lib/ldap');
var sinon = require('sinon'); var sinon = require('sinon');
var Promise = require('bluebird'); var Promise = require('bluebird');
var assert = require('assert'); var assert = require('assert');
var ldapjs = require('ldapjs');
var winston = require('winston');
describe('test ldap validation', function() { describe('test ldap validation', function() {
var ldap_client; var ldap_client;
var ldap, ldapjs;
var ldap_config;
beforeEach(function() { beforeEach(function() {
ldap_client = { ldap_client = {
bind: sinon.stub(), bind: sinon.stub(),
search: sinon.stub(), search: sinon.stub(),
modify: 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 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); describe('test update password', test_update_password);
function test_binding() { function test_binding() {
function test_validate() { function test_bind() {
var username = 'user'; var username = "username";
var password = 'password'; var password = "password";
var users_dn = 'dc=example,dc=com'; return ldap.bind(username, password);
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() {
ldap_client.bind.yields(); 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 username = 'user';
var password = 'password'; var password = 'password';
var user_search_base = 'dc=example,dc=com'; ldap_client.bind.withArgs('uid=user,ou=users,dc=example,dc=com').yields();
var user_search_filter = 'uid'; return ldap.bind(username, password);
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) { it('should default to cn user search filter if no filter provided', function() {
var username = 'user'; var username = 'user';
var password = 'password'; var password = 'password';
var user_search_base = 'dc=example,dc=com'; ldap_client.bind.withArgs('cn=user,ou=users,dc=example,dc=com').yields();
ldap_client.bind = sinon.spy(function(dn) { return ldap.bind(username, password);
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();
}); });
it('should not bind the user if wrong credentials provided', function() { it('should not bind the user if wrong credentials provided', function() {
ldap_client.bind.yields('wrong credentials'); ldap_client.bind.yields('wrong credentials');
var promise = test_validate(); var promise = test_bind();
return promise.catch(function() { return promise.catch(function() {
return Promise.resolve(); return Promise.resolve();
}); });
}); });
} }
function test_get_email() { function test_get_emails() {
it('should retrieve the email of an existing user', function() { var res_emitter;
var expected_doc = {}; var expected_doc;
beforeEach(function() {
expected_doc = {};
expected_doc.object = {}; expected_doc.object = {};
expected_doc.object.mail = 'user@example.com'; expected_doc.object.mail = 'user@example.com';
var res_emitter = {};
res_emitter = {};
res_emitter.on = sinon.spy(function(event, fn) { res_emitter.on = sinon.spy(function(event, fn) {
if(event != 'error') fn(expected_doc) if(event != 'error') fn(expected_doc)
}); });
});
it('should retrieve the email of an existing user', function() {
ldap_client.search.yields(undefined, res_emitter); ldap_client.search.yields(undefined, res_emitter);
return ldap.get_email(ldap_client, 'user', 'dc=example,dc=com') return ldap.get_emails('user')
.then(function(doc) { .then(function(emails) {
assert.deepEqual(doc, expected_doc.object); assert.deepEqual(emails, [expected_doc.object.mail]);
return Promise.resolve(); return Promise.resolve();
}) })
}); });
it('should use the user filter', function(done) { it('should retrieve email for user with uid name attribute', function() {
ldap_client.search = sinon.spy(function(dn) { ldap_config.user_name_attribute = 'uid';
if(dn == 'uid=username,ou=users,dc=example,dc=com') done(); 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 = {}; var expected_doc = {};
expected_doc.mail = []; expected_doc.mail = [];
expected_doc.mail.push('user@example.com'); expected_doc.mail.push('user@example.com');
ldap_client.search.yields('error'); ldap_client.search.yields('error');
ldap.get_email(ldap_client, 'user', 'dc=example,dc=com') return ldap.get_emails('user')
.catch(function() { .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(); 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() { function test_update_password() {
it('should update the password successfully', function(done) { it('should update the password successfully', function() {
var change = {}; var change = {};
change.operation = 'replace'; change.operation = 'replace';
change.modification = {}; change.modification = {};
change.modification.userPassword = 'new-password'; change.modification.userPassword = 'new-password';
var config = {}; var userdn = 'cn=user,ou=users,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';
var ldapjs = {};
ldapjs.Change = sinon.spy();
ldap_client.bind.yields(undefined); ldap_client.bind.yields(undefined);
ldap_client.modify.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() { .then(function() {
assert.deepEqual(ldap_client.modify.getCall(0).args[0], userdn); assert.deepEqual(ldap_client.modify.getCall(0).args[0], userdn);
assert.deepEqual(ldapjs.Change.getCall(0).args[0].operation, change.operation); assert.deepEqual(ldapjs.Change.getCall(0).args[0].operation, change.operation);
var userPassword = ldapjs.Change.getCall(0).args[0].modification.userPassword; var userPassword = ldapjs.Change.getCall(0).args[0].modification.userPassword;
assert(/{SSHA}/.test(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.bind.yields(undefined);
ldap_client.modify.yields('Error'); ldap_client.modify.yields('Error');
var config = {}; return ldap.update_password('user', 'new-password')
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)
.catch(function() { .catch(function() {
done(); return Promise.resolve();
}) })
}); });
it('should use the user filter', function(done) { it('should update password of user using particular user name attribute', function() {
var ldapjs = {}; ldap_config.user_name_attribute = 'uid';
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.bind.yields(undefined);
ldap_client.modify = sinon.spy(function(dn) { ldap_client.modify.withArgs('uid=username,ou=users,dc=example,dc=com').yields();
if(dn == 'uid=username,ou=users,dc=example,dc=com') done(); return ldap.update_password('username', 'newpass');
});
ldap.update_password(ldap_client, ldapjs, 'username', 'newpass', config)
}); });
} }
}); });

View File

@ -1,5 +1,6 @@
var server = require('../../src/lib/server'); var server = require('../../src/lib/server');
var Ldap = require('../../src/lib/ldap');
var Promise = require('bluebird'); var Promise = require('bluebird');
var request = Promise.promisifyAll(require('request')); var request = Promise.promisifyAll(require('request'));
@ -8,6 +9,9 @@ var speakeasy = require('speakeasy');
var sinon = require('sinon'); var sinon = require('sinon');
var MockDate = require('mockdate'); var MockDate = require('mockdate');
var session = require('express-session'); var session = require('express-session');
var winston = require('winston');
var speakeasy = require('speakeasy');
var ldapjs = require('ldapjs');
var PORT = 8090; var PORT = 8090;
var BASE_URL = 'http://localhost:' + PORT; var BASE_URL = 'http://localhost:' + PORT;
@ -19,26 +23,22 @@ describe('test the server', function() {
var u2f, nedb; var u2f, nedb;
var transporter; var transporter;
var collection; var collection;
var ldap_client = {
bind: sinon.stub(),
search: sinon.stub(),
modify: sinon.stub(),
};
var ldap = {
Change: sinon.spy()
}
beforeEach(function(done) { beforeEach(function(done) {
var config = { var config = {
port: PORT, port: PORT,
totp_secret: 'totp_secret', totp_secret: 'totp_secret',
ldap_url: 'ldap://127.0.0.1:389', ldap: {
ldap_user_search_base: 'ou=users,dc=example,dc=com', url: 'ldap://127.0.0.1:389',
ldap_user_search_filter: 'cn', base_dn: 'ou=users,dc=example,dc=com',
ldap_user: 'cn=admin,dc=example,dc=com', user_name_attribute: 'cn',
ldap_password: 'password', user: 'cn=admin,dc=example,dc=com',
session_secret: 'session_secret', password: 'password',
session_max_age: 50000, },
session: {
secret: 'session_secret',
expiration: 50000,
},
store_in_memory: true, store_in_memory: true,
notifier: { notifier: {
gmail: { gmail: {
@ -48,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 = {};
u2f.startRegistration = sinon.stub(); u2f.startRegistration = sinon.stub();
u2f.finishRegistration = sinon.stub(); u2f.finishRegistration = sinon.stub();
@ -64,15 +77,15 @@ describe('test the server', function() {
return transporter; return transporter;
  });   });
var search_doc = { ldap_document = {
object: { object: {
mail: 'test_ok@example.com' mail: 'test_ok@example.com',
} }
}; };
var search_res = {}; var search_res = {};
search_res.on = sinon.spy(function(event, fn) { 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', ldap_client.bind.withArgs('cn=test_ok,ou=users,dc=example,dc=com',
@ -90,10 +103,12 @@ describe('test the server', function() {
deps.u2f = u2f; deps.u2f = u2f;
deps.nedb = nedb; deps.nedb = nedb;
deps.nodemailer = nodemailer; deps.nodemailer = nodemailer;
deps.ldap = ldap; deps.ldapjs = ldap;
deps.session = session; deps.session = session;
deps.winston = winston;
deps.speakeasy = speakeasy;
_server = server.run(config, ldap_client, deps, function() { _server = server.run(config, deps, function() {
done(); done();
}); });
}); });
@ -348,7 +363,6 @@ describe('test the server', function() {
return requests.failing_first_factor(j); return requests.failing_first_factor(j);
}) })
.then(function(res) { .then(function(res) {
console.log('coucou');
assert.equal(res.statusCode, 401, 'first factor failed'); assert.equal(res.statusCode, 401, 'first factor failed');
return requests.failing_first_factor(j); return requests.failing_first_factor(j);
}) })

View File

@ -26,7 +26,12 @@ describe('test server configuration', function() {
deps = {}; deps = {};
deps.nedb = require('nedb'); deps.nedb = require('nedb');
deps.winston = sinon.spy();
deps.nodemailer = nodemailer; deps.nodemailer = nodemailer;
deps.ldapjs = {};
deps.ldapjs.createClient = sinon.spy(function() {
return { on: sinon.spy() };
});
deps.session = sinon.spy(function() { deps.session = sinon.spy(function() {
return function(req, res, next) { next(); }; return function(req, res, next) { next(); };
}); });
@ -34,8 +39,11 @@ describe('test server configuration', function() {
it('should set cookie scope to domain set in the config', function() { it('should set cookie scope to domain set in the config', function() {
config.session_domain = 'example.com'; config.session = {};
server.run(config, undefined, deps); config.session.domain = 'example.com';
config.ldap = {};
config.ldap.url = 'http://ldap';
server.run(config, deps);
assert(deps.session.calledOnce); assert(deps.session.calledOnce);
assert.equal(deps.session.getCall(0).args[0].cookie.domain, 'example.com'); assert.equal(deps.session.getCall(0).args[0].cookie.domain, 'example.com');