mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
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:
commit
1910ad520d
28
README.md
28
README.md
|
@ -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 secret1.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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
accepted before getting to the login page:
|
||||
|
||||
![first-factor-page](https://raw.githubusercontent.com/clems4ever/authelia/master/images/first_factor.png)
|
||||
|
||||
### 1st factor: LDAP
|
||||
An LDAP server has been deployed for you with the following credentials: **user/password**.
|
||||
### 1st factor: LDAP and ACL
|
||||
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
|
||||
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.
|
||||
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
|
||||
### Configuration
|
||||
The configuration of the server is defined in the file
|
||||
|
|
|
@ -1,17 +1,67 @@
|
|||
|
||||
# The port to listen on
|
||||
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 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
|
||||
#
|
||||
# _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=
|
||||
#
|
||||
|
|
|
@ -3,8 +3,81 @@
|
|||
<title>Home page</title>
|
||||
</head>
|
||||
<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/>
|
||||
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 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>.
|
||||
<h1>Access the secret</h1>
|
||||
You need to log in to access the secret!<br/><br/>
|
||||
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>
|
||||
</html>
|
||||
|
|
|
@ -60,7 +60,9 @@ http {
|
|||
listen 443 ssl;
|
||||
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_certificate /etc/ssl/server.crt;
|
||||
|
@ -73,8 +75,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;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<title>Secret</title>
|
||||
</head>
|
||||
<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>
|
||||
</html>
|
||||
|
|
34
src/index.js
34
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,35 +24,13 @@ console.log('Parse configuration file: %s', 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 = {};
|
||||
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(config, ldap_client, deps);
|
||||
server.run(yaml_config, deps);
|
||||
|
|
84
src/lib/access_control.js
Normal file
84
src/lib/access_control.js
Normal 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
17
src/lib/config_adapter.js
Normal 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')
|
||||
}
|
||||
};
|
||||
|
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,29 @@
|
|||
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, 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) {
|
||||
var logger = req.app.get('logger');
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
if(!username || !password) {
|
||||
|
@ -15,59 +33,70 @@ 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');
|
||||
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: 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];
|
||||
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);
|
||||
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,21 @@ 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');
|
||||
|
||||
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 ||
|
||||
!req.session.auth_session.second_factor)
|
||||
return Promise.reject('First or second factor not validated');
|
||||
|
@ -28,6 +45,7 @@ function verify(req, res) {
|
|||
res.send();
|
||||
})
|
||||
.catch(function(err) {
|
||||
req.app.get('logger').error(err);
|
||||
res.status(401);
|
||||
res.send();
|
||||
});
|
||||
|
|
|
@ -5,15 +5,18 @@ 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');
|
||||
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 public_html_directory = path.resolve(__dirname, '../public_html');
|
||||
var datastore_options = {};
|
||||
|
@ -42,22 +45,24 @@ function run(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);
|
||||
var access_control = AccessControl(deps.winston, config.access_control);
|
||||
|
||||
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);
|
||||
app.set('authentication regulator', regulator);
|
||||
app.set('config', config);
|
||||
app.set('access control', access_control);
|
||||
|
||||
setup_endpoints(app);
|
||||
|
||||
|
|
|
@ -5,35 +5,30 @@ 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');
|
||||
var AccessControl = require('../../../src/lib/access_control');
|
||||
|
||||
describe('test the first factor validation route', function() {
|
||||
var req, res;
|
||||
var ldap_interface_mock;
|
||||
var emails;
|
||||
var search_res_ok;
|
||||
var regulator;
|
||||
var access_control;
|
||||
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();
|
||||
regulator.regulate = sinon.stub();
|
||||
|
@ -41,11 +36,22 @@ describe('test the first factor validation route', function() {
|
|||
regulator.mark.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();
|
||||
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);
|
||||
app_get.withArgs('access control').returns(access_control);
|
||||
|
||||
req = {
|
||||
app: {
|
||||
|
@ -75,43 +81,83 @@ 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();
|
||||
describe('store the ACL matcher in the auth session', function() {
|
||||
it('should store the allowed domains in the auth session', function() {
|
||||
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) {
|
||||
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 +168,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,41 @@
|
|||
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;
|
||||
var acl_matcher;
|
||||
|
||||
beforeEach(function() {
|
||||
acl_matcher = {
|
||||
is_domain_allowed: sinon.stub().returns(true)
|
||||
};
|
||||
var access_control = {
|
||||
matcher: acl_matcher
|
||||
}
|
||||
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);
|
||||
req.app.get.withArgs('access control').returns(access_control);
|
||||
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',
|
||||
allowed_domains: ['*']
|
||||
};
|
||||
|
||||
res.send = sinon.spy(function() {
|
||||
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() {
|
||||
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 +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() {
|
||||
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 });
|
||||
});
|
||||
|
||||
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 +93,25 @@ describe('test authentication token verification', function() {
|
|||
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() {
|
||||
return test_unauthorized({ first_factor: true });
|
||||
});
|
||||
|
|
160
test/unitary/test_access_control.js
Normal file
160
test/unitary/test_access_control.js
Normal 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));
|
||||
});
|
||||
});
|
||||
});
|
76
test/unitary/test_config_adapter.js
Normal file
76
test/unitary/test_config_adapter.js
Normal 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');
|
||||
});
|
||||
});
|
|
@ -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() {
|
||||
|
@ -53,10 +60,14 @@ describe('test data persistence', function() {
|
|||
config = {
|
||||
port: PORT,
|
||||
totp_secret: 'totp_secret',
|
||||
ldap_url: 'ldap://127.0.0.1:389',
|
||||
ldap_user_search_base: 'ou=users,dc=example,dc=com',
|
||||
session_secret: 'session_secret',
|
||||
session_max_age: 50000,
|
||||
ldap: {
|
||||
url: 'ldap://127.0.0.1:389',
|
||||
base_dn: 'ou=users,dc=example,dc=com',
|
||||
},
|
||||
session: {
|
||||
secret: 'session_secret',
|
||||
expiration: 50000,
|
||||
},
|
||||
store_directory: tmpDir.name,
|
||||
notifier: { gmail: { user: 'user@example.com', pass: 'password' } }
|
||||
};
|
||||
|
@ -90,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);
|
||||
|
@ -112,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;
|
||||
|
@ -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) {
|
||||
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,26 +23,22 @@ 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 = {
|
||||
port: PORT,
|
||||
totp_secret: 'totp_secret',
|
||||
ldap_url: 'ldap://127.0.0.1:389',
|
||||
ldap_user_search_base: 'ou=users,dc=example,dc=com',
|
||||
ldap_user_search_filter: 'cn',
|
||||
ldap_user: 'cn=admin,dc=example,dc=com',
|
||||
ldap_password: 'password',
|
||||
session_secret: 'session_secret',
|
||||
session_max_age: 50000,
|
||||
ldap: {
|
||||
url: 'ldap://127.0.0.1:389',
|
||||
base_dn: 'ou=users,dc=example,dc=com',
|
||||
user_name_attribute: 'cn',
|
||||
user: 'cn=admin,dc=example,dc=com',
|
||||
password: 'password',
|
||||
},
|
||||
session: {
|
||||
secret: 'session_secret',
|
||||
expiration: 50000,
|
||||
},
|
||||
store_in_memory: true,
|
||||
notifier: {
|
||||
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.startRegistration = sinon.stub();
|
||||
u2f.finishRegistration = sinon.stub();
|
||||
|
@ -64,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',
|
||||
|
@ -90,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();
|
||||
});
|
||||
});
|
||||
|
@ -348,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(); };
|
||||
});
|
||||
|
@ -34,8 +39,11 @@ describe('test server configuration', function() {
|
|||
|
||||
|
||||
it('should set cookie scope to domain set in the config', function() {
|
||||
config.session_domain = 'example.com';
|
||||
server.run(config, undefined, deps);
|
||||
config.session = {};
|
||||
config.session.domain = 'example.com';
|
||||
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
Block a user