Merge branch 'devel' of https://github.com/thiagofernando/wekan into thiagofernando-devel

reviewable/pr2400/r1
Lauri Ojansivu 6 years ago
commit 6f98be5608
  1. 182
      packages/wekan-ldap/server/ldap.js
  2. 52
      packages/wekan-ldap/server/loginHandler.js

@ -1,41 +1,44 @@
import ldapjs from 'ldapjs';
import util from 'util';
import Bunyan from 'bunyan';
import { log_debug, log_info, log_warn, log_error } from './logger';
import {log_debug, log_info, log_warn, log_error} from './logger';
export default class LDAP {
constructor(){
constructor() {
this.ldapjs = ldapjs;
this.connected = false;
this.options = {
host: this.constructor.settings_get('LDAP_HOST'),
port: this.constructor.settings_get('LDAP_PORT'),
Reconnect: this.constructor.settings_get('LDAP_RECONNECT'),
timeout: this.constructor.settings_get('LDAP_TIMEOUT'),
connect_timeout: this.constructor.settings_get('LDAP_CONNECT_TIMEOUT'),
idle_timeout: this.constructor.settings_get('LDAP_IDLE_TIMEOUT'),
encryption: this.constructor.settings_get('LDAP_ENCRYPTION'),
ca_cert: this.constructor.settings_get('LDAP_CA_CERT'),
reject_unauthorized: this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false,
Authentication: this.constructor.settings_get('LDAP_AUTHENTIFICATION'),
Authentication_UserDN: this.constructor.settings_get('LDAP_AUTHENTIFICATION_USERDN'),
Authentication_Password: this.constructor.settings_get('LDAP_AUTHENTIFICATION_PASSWORD'),
Authentication_Fallback: this.constructor.settings_get('LDAP_LOGIN_FALLBACK'),
BaseDN: this.constructor.settings_get('LDAP_BASEDN'),
Internal_Log_Level: this.constructor.settings_get('INTERNAL_LOG_LEVEL'),
User_Search_Filter: this.constructor.settings_get('LDAP_USER_SEARCH_FILTER'),
User_Search_Scope: this.constructor.settings_get('LDAP_USER_SEARCH_SCOPE'),
User_Search_Field: this.constructor.settings_get('LDAP_USER_SEARCH_FIELD'),
Search_Page_Size: this.constructor.settings_get('LDAP_SEARCH_PAGE_SIZE'),
Search_Size_Limit: this.constructor.settings_get('LDAP_SEARCH_SIZE_LIMIT'),
group_filter_enabled: this.constructor.settings_get('LDAP_GROUP_FILTER_ENABLE'),
group_filter_object_class: this.constructor.settings_get('LDAP_GROUP_FILTER_OBJECTCLASS'),
group_filter_group_id_attribute: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE'),
host : this.constructor.settings_get('LDAP_HOST'),
port : this.constructor.settings_get('LDAP_PORT'),
Reconnect : this.constructor.settings_get('LDAP_RECONNECT'),
timeout : this.constructor.settings_get('LDAP_TIMEOUT'),
connect_timeout : this.constructor.settings_get('LDAP_CONNECT_TIMEOUT'),
idle_timeout : this.constructor.settings_get('LDAP_IDLE_TIMEOUT'),
encryption : this.constructor.settings_get('LDAP_ENCRYPTION'),
ca_cert : this.constructor.settings_get('LDAP_CA_CERT'),
reject_unauthorized : this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false,
Authentication : this.constructor.settings_get('LDAP_AUTHENTIFICATION'),
Authentication_UserDN : this.constructor.settings_get('LDAP_AUTHENTIFICATION_USERDN'),
Authentication_Password : this.constructor.settings_get('LDAP_AUTHENTIFICATION_PASSWORD'),
Authentication_Fallback : this.constructor.settings_get('LDAP_LOGIN_FALLBACK'),
BaseDN : this.constructor.settings_get('LDAP_BASEDN'),
Internal_Log_Level : this.constructor.settings_get('INTERNAL_LOG_LEVEL'),
User_Authentication : this.constructor.settings_get('LDAP_USER_AUTHENTICATION'),
User_Attributes : this.constructor.settings_get('LDAP_USER_ATTRIBUTES'),
User_Search_Filter : this.constructor.settings_get('LDAP_USER_SEARCH_FILTER'),
User_Search_Scope : this.constructor.settings_get('LDAP_USER_SEARCH_SCOPE'),
User_Search_Field : this.constructor.settings_get('LDAP_USER_SEARCH_FIELD'),
Search_Page_Size : this.constructor.settings_get('LDAP_SEARCH_PAGE_SIZE'),
Search_Size_Limit : this.constructor.settings_get('LDAP_SEARCH_SIZE_LIMIT'),
group_filter_enabled : this.constructor.settings_get('LDAP_GROUP_FILTER_ENABLE'),
group_filter_object_class : this.constructor.settings_get('LDAP_GROUP_FILTER_OBJECTCLASS'),
group_filter_group_id_attribute : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE'),
group_filter_group_member_attribute: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE'),
group_filter_group_member_format: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT'),
group_filter_group_name: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_NAME'),
group_filter_group_member_format : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT'),
group_filter_group_name : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_NAME'),
};
}
@ -52,14 +55,16 @@ export default class LDAP {
log_warn(`Lookup for unset variable: ${name}`);
}
}
connectSync(...args) {
if (!this._connectSync) {
if (!this._connectSync) {
this._connectSync = Meteor.wrapAsync(this.connectAsync, this);
}
return this._connectSync(...args);
}
searchAllSync(...args) {
if (!this._searchAllSync) {
this._searchAllSync = Meteor.wrapAsync(this.searchAllAsync, this);
}
@ -72,19 +77,19 @@ export default class LDAP {
let replied = false;
const connectionOptions = {
url: `${ this.options.host }:${ this.options.port }`,
timeout: this.options.timeout,
url : `${this.options.host}:${this.options.port}`,
timeout : this.options.timeout,
connectTimeout: this.options.connect_timeout,
idleTimeout: this.options.idle_timeout,
reconnect: this.options.Reconnect,
idleTimeout : this.options.idle_timeout,
reconnect : this.options.Reconnect,
};
if (this.options.Internal_Log_Level !== 'disabled') {
connectionOptions.log = new Bunyan({
name: 'ldapjs',
name : 'ldapjs',
component: 'client',
stream: process.stderr,
level: this.options.Internal_Log_Level,
stream : process.stderr,
level : this.options.Internal_Log_Level,
});
}
@ -95,8 +100,8 @@ export default class LDAP {
if (this.options.ca_cert && this.options.ca_cert !== '') {
// Split CA cert into array of strings
const chainLines = this.constructor.settings_get('LDAP_CA_CERT').split('\n');
let cert = [];
const ca = [];
let cert = [];
const ca = [];
chainLines.forEach((line) => {
cert.push(line);
if (line.match(/-END CERTIFICATE-/)) {
@ -108,14 +113,14 @@ export default class LDAP {
}
if (this.options.encryption === 'ssl') {
connectionOptions.url = `ldaps://${ connectionOptions.url }`;
connectionOptions.url = `ldaps://${connectionOptions.url}`;
connectionOptions.tlsOptions = tlsOptions;
} else {
connectionOptions.url = `ldap://${ connectionOptions.url }`;
connectionOptions.url = `ldap://${connectionOptions.url}`;
}
log_info('Connecting', connectionOptions.url);
log_debug(`connectionOptions${ util.inspect(connectionOptions)}`);
log_debug(`connectionOptions${util.inspect(connectionOptions)}`);
this.client = ldapjs.createClient(connectionOptions);
@ -189,23 +194,42 @@ export default class LDAP {
if (this.options.User_Search_Filter !== '') {
if (this.options.User_Search_Filter[0] === '(') {
filter.push(`${ this.options.User_Search_Filter }`);
filter.push(`${this.options.User_Search_Filter}`);
} else {
filter.push(`(${ this.options.User_Search_Filter })`);
filter.push(`(${this.options.User_Search_Filter})`);
}
}
const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${ item }=${ username })`);
const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${item}=${username})`);
if (usernameFilter.length === 0) {
log_error('LDAP_LDAP_User_Search_Field not defined');
} else if (usernameFilter.length === 1) {
filter.push(`${ usernameFilter[0] }`);
filter.push(`${usernameFilter[0]}`);
} else {
filter.push(`(|${ usernameFilter.join('') })`);
filter.push(`(|${usernameFilter.join('')})`);
}
return `(&${filter.join('')})`;
}
bindUserIfNecessary(username, password) {
if (this.domainBinded === true) {
return;
}
if (!this.options.User_Authentication) {
return;
}
return `(&${ filter.join('') })`;
if (!this.options.BaseDN) throw new Error('BaseDN is not provided');
const userDn = `uid=${username},${this.options.BaseDN}`;
this.bindSync(userDn, password);
this.domainBinded = true;
}
bindIfNecessary() {
@ -218,22 +242,24 @@ export default class LDAP {
}
log_info('Binding UserDN', this.options.Authentication_UserDN);
this.bindSync(this.options.Authentication_UserDN, this.options.Authentication_Password);
this.domainBinded = true;
}
searchUsersSync(username, page) {
this.bindIfNecessary();
const searchOptions = {
filter: this.getUserFilter(username),
scope: this.options.User_Search_Scope || 'sub',
filter : this.getUserFilter(username),
scope : this.options.User_Search_Scope || 'sub',
sizeLimit: this.options.Search_Size_Limit,
};
if (!!this.options.User_Attributes) searchOptions.attributes = this.options.User_Attributes.split(',');
if (this.options.Search_Page_Size > 0) {
searchOptions.paged = {
pageSize: this.options.Search_Page_Size,
pageSize : this.options.Search_Page_Size,
pagePause: !!page,
};
}
@ -266,11 +292,11 @@ export default class LDAP {
Unique_Identifier_Field.forEach((item) => {
filters.push(new this.ldapjs.filters.EqualityFilter({
attribute: item,
value: new Buffer(id, 'hex'),
value : new Buffer(id, 'hex'),
}));
});
filter = new this.ldapjs.filters.OrFilter({filters});
filter = new this.ldapjs.filters.OrFilter({ filters });
}
const searchOptions = {
@ -300,7 +326,7 @@ export default class LDAP {
const searchOptions = {
filter: this.getUserFilter(username),
scope: this.options.User_Search_Scope || 'sub',
scope : this.options.User_Search_Scope || 'sub',
};
log_info('Searching user', username);
@ -320,7 +346,7 @@ export default class LDAP {
return result[0];
}
getUserGroups(username, ldapUser){
getUserGroups(username, ldapUser) {
if (!this.options.group_filter_enabled) {
return true;
}
@ -328,13 +354,13 @@ export default class LDAP {
const filter = ['(&'];
if (this.options.group_filter_object_class !== '') {
filter.push(`(objectclass=${ this.options.group_filter_object_class })`);
filter.push(`(objectclass=${this.options.group_filter_object_class})`);
}
if (this.options.group_filter_group_member_attribute !== '') {
const format_value = ldapUser[this.options.group_filter_group_member_format];
if( format_value ) {
filter.push(`(${ this.options.group_filter_group_member_attribute }=${ format_value })`);
if (format_value) {
filter.push(`(${this.options.group_filter_group_member_attribute}=${format_value})`);
}
}
@ -342,7 +368,7 @@ export default class LDAP {
const searchOptions = {
filter: filter.join('').replace(/#{username}/g, username),
scope: 'sub',
scope : 'sub',
};
log_debug('Group list filter LDAP:', searchOptions.filter);
@ -354,11 +380,11 @@ export default class LDAP {
}
const grp_identifier = this.options.group_filter_group_id_attribute || 'cn';
const groups = [];
const groups = [];
result.map((item) => {
groups.push( item[ grp_identifier ] );
groups.push(item[grp_identifier]);
});
log_debug(`Groups: ${ groups.join(', ')}`);
log_debug(`Groups: ${groups.join(', ')}`);
return groups;
}
@ -373,24 +399,24 @@ export default class LDAP {
const filter = ['(&'];
if (this.options.group_filter_object_class !== '') {
filter.push(`(objectclass=${ this.options.group_filter_object_class })`);
filter.push(`(objectclass=${this.options.group_filter_object_class})`);
}
if (this.options.group_filter_group_member_attribute !== '') {
const format_value = ldapUser[this.options.group_filter_group_member_format];
if( format_value ) {
filter.push(`(${ this.options.group_filter_group_member_attribute }=${ format_value })`);
if (format_value) {
filter.push(`(${this.options.group_filter_group_member_attribute}=${format_value})`);
}
}
if (this.options.group_filter_group_id_attribute !== '') {
filter.push(`(${ this.options.group_filter_group_id_attribute }=${ this.options.group_filter_group_name })`);
filter.push(`(${this.options.group_filter_group_id_attribute}=${this.options.group_filter_group_name})`);
}
filter.push(')');
const searchOptions = {
filter: filter.join('').replace(/#{username}/g, username),
scope: 'sub',
scope : 'sub',
};
log_debug('Group filter LDAP:', searchOptions.filter);
@ -426,15 +452,17 @@ export default class LDAP {
searchAllPaged(BaseDN, options, page) {
this.bindIfNecessary();
const processPage = ({entries, title, end, next}) => {
const processPage = ({ entries, title, end, next }) => {
log_info(title);
// Force LDAP idle to wait the record processing
this.client._updateIdle(true);
page(null, entries, {end, next: () => {
// Reset idle timer
this.client._updateIdle();
next && next();
}});
page(null, entries, {
end, next: () => {
// Reset idle timer
this.client._updateIdle();
next && next();
}
});
};
this.client.search(BaseDN, options, (error, res) => {
@ -461,7 +489,7 @@ export default class LDAP {
processPage({
entries,
title: 'Internal Page',
end: false,
end : false,
});
entries = [];
}
@ -473,14 +501,14 @@ export default class LDAP {
processPage({
entries,
title: 'Final Page',
end: true,
end : true,
});
} else if (entries.length) {
log_info('Page');
processPage({
entries,
title: 'Page',
end: false,
end : false,
next,
});
entries = [];
@ -492,7 +520,7 @@ export default class LDAP {
processPage({
entries,
title: 'Final Page',
end: true,
end : true,
});
entries = [];
}
@ -547,7 +575,7 @@ export default class LDAP {
}
disconnect() {
this.connected = false;
this.connected = false;
this.domainBinded = false;
log_info('Disconecting');
this.client.unbind();

@ -41,28 +41,38 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
let ldapUser;
try {
ldap.connectSync();
const users = ldap.searchUsersSync(loginRequest.username);
if (users.length !== 1) {
log_info('Search returned', users.length, 'record(s) for', loginRequest.username);
throw new Error('User not Found');
}
ldap.connectSync();
if (!!LDAP.settings_get('LDAP_USER_AUTHENTICATION')) {
ldap.bindUserIfNecessary(loginRequest.username, loginRequest.ldapPass);
ldapUser = ldap.searchUsersSync(loginRequest.username)[0];
} else {
const users = ldap.searchUsersSync(loginRequest.username);
if (users.length !== 1) {
log_info('Search returned', users.length, 'record(s) for', loginRequest.username);
throw new Error('User not Found');
}
if (ldap.authSync(users[0].dn, loginRequest.ldapPass) === true) {
if (ldap.isUserInGroup(loginRequest.username, users[0])) {
ldapUser = users[0];
} else {
throw new Error('User not in a valid group');
}
} else {
log_info('Wrong password for', loginRequest.username);
}
}
if (ldap.authSync(users[0].dn, loginRequest.ldapPass) === true) {
if (ldap.isUserInGroup(loginRequest.username, users[0])) {
ldapUser = users[0];
} else {
throw new Error('User not in a valid group');
}
} else {
log_info('Wrong password for', loginRequest.username);
}
} catch (error) {
log_error(error);
log_error(error);
}
if (ldapUser === undefined) {
if (!ldapUser) {
if (LDAP.settings_get('LDAP_LOGIN_FALLBACK') === true) {
return fallbackDefaultAccountSystem(self, loginRequest.username, loginRequest.ldapPass);
}
@ -76,8 +86,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
const Unique_Identifier_Field = getLdapUserUniqueID(ldapUser);
let user;
// Attempt to find user by unique identifier
// Attempt to find user by unique identifier
if (Unique_Identifier_Field) {
userQuery = {
@ -88,14 +97,14 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
log_debug('userQuery', userQuery);
user = Meteor.users.findOne(userQuery);
}
}
// Attempt to find user by username
let username;
let email;
if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') {
if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') {
username = slug(getLdapUsername(ldapUser));
} else {
username = slug(loginRequest.username);
@ -105,6 +114,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
email = getLdapEmail(ldapUser);
}
if (!user) {
if(email && LDAP.settings_get('LDAP_EMAIL_MATCH_REQUIRE') === true) {
if(LDAP.settings_get('LDAP_EMAIL_MATCH_VERIFIED') === true) {

Loading…
Cancel
Save