|
|
|
@ -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(); |
|
|
|
|