From 543a41d971208c5c6632d9b47af0fd315946886e Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Sun, 28 Apr 2019 02:46:17 -0400 Subject: [PATCH 01/16] Init commit of LDAP user role / group synchronization --- app/ldap/server/settings.js | 12 +++ app/ldap/server/sync.js | 93 ++++++++++++++++++++-- packages/rocketchat-i18n/i18n/en.i18n.json | 9 +++ 3 files changed, 106 insertions(+), 8 deletions(-) diff --git a/app/ldap/server/settings.js b/app/ldap/server/settings.js index 033c48caf73e..c1aa5313a285 100644 --- a/app/ldap/server/settings.js +++ b/app/ldap/server/settings.js @@ -14,6 +14,10 @@ settings.addGroup('LDAP', function() { enableQuery, { _id: 'LDAP_Sync_User_Data', value: true }, ]; + const syncGroupsQuery = [ + enableQuery, + { _id: 'LDAP_Sync_User_Data_Groups', value: true }, + ]; const groupFilterQuery = [ enableQuery, { _id: 'LDAP_Group_Filter_Enable', value: true }, @@ -84,6 +88,14 @@ settings.addGroup('LDAP', function() { this.add('LDAP_Sync_User_Data', false, { type: 'boolean', enableQuery }); this.add('LDAP_Sync_User_Data_FieldMap', '{"cn":"name", "mail":"email"}', { type: 'string', enableQuery: syncDataQuery }); + + this.add('LDAP_Sync_User_Data_Groups', false, { type: 'boolean', enableQuery }); + this.add('LDAP_Sync_User_Data_Groups_Filter', '(&(cn=#{groupName})(memberUid=#{username}))', { type: 'string', enableQuery: syncGroupsQuery }); + this.add('LDAP_Sync_User_Data_Groups_AutoRemove', false, { type: 'boolean', enableQuery: syncGroupsQuery }); + this.add('LDAP_Sync_User_Data_Groups_Filter', '(&(cn=#{groupName})(memberUid=#{username}))', { type: 'string', enableQuery: syncGroupsQuery }); + this.add('LDAP_Sync_User_Data_Groups_BaseDN', '', { type: 'string', enableQuery: syncGroupsQuery }); + this.add('LDAP_Sync_User_Data_GroupsMap', '{"rocket-admin":"admin", "tech-support":"support"}', { type: 'string', enableQuery: syncGroupsQuery }); + this.add('LDAP_Sync_User_Avatar', true, { type: 'boolean', enableQuery }); this.add('LDAP_Background_Sync', false, { type: 'boolean', enableQuery }); diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index 3b94bd9a43bc..f15aa5736fd0 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -4,7 +4,7 @@ import { Accounts } from 'meteor/accounts-base'; import { RocketChatFile } from '../../file'; import { settings } from '../../settings'; import { Notifications } from '../../notifications'; -import { Users } from '../../models'; +import { Users, Roles } from '../../models'; import { Logger } from '../../logger'; import { _setRealName, _setUsername } from '../../lib'; import { templateVarHandler } from '../../utils'; @@ -14,6 +14,7 @@ import _ from 'underscore'; import LDAP from './ldap'; const logger = new Logger('LDAPSync', {}); +let ldap = new LDAP(); export function slug(text) { if (settings.get('UTF8_Names_Slugify') !== true) { @@ -171,6 +172,62 @@ export function getDataToSyncUserData(ldapUser, user) { return userData; } } +export function getRolesToSyncUserRoles(ldapUser, user) { + const syncUserRoles = settings.get('LDAP_Sync_User_Data_Groups'); + const syncUserRolesAutoRemove = settings.get('LDAP_Sync_User_Data_Groups_AutoRemove'); + const syncUserRolesFieldMap = settings.get('LDAP_Sync_User_Data_GroupsMap').trim(); + const syncUserRolesFilter = settings.get('LDAP_Sync_User_Data_Groups_Filter').trim(); + const syncUserRolesBaseDN = settings.get('LDAP_Sync_User_Data_Groups_BaseDN').trim(); + + const roles = Roles.find({}, { + fields: { + _updatedAt: 0, + }, + }).fetch(); + const userRoles = []; + if (syncUserRoles && syncUserRolesFieldMap) { + const fieldMap = JSON.parse(syncUserRolesFieldMap); + _.map(fieldMap, function(userField, ldapField) { + const [roleName] = userField.split(/\.(.+)/); + if (!_.find(roles, (el) => el._id === roleName)) { + logger.debug(`User Role doesn't exist: ${ roleName }`); + return; + } else { + logger.debug(`User role exists for mapping ${ roleName } -> ${ ldapField }`); + + const searchOptions = { + filter: syncUserRolesFilter.replace(/#{username}/g, user.username).replace(/#{groupName}/g, ldapField), + scope: 'sub', + }; + + const result = ldap.searchAllSync(syncUserRolesBaseDN, searchOptions); + if (!Array.isArray(result) || result.length === 0) { + logger.debug(`${ user.username } is not in ${ roleName } group!!!`); + if (syncUserRolesAutoRemove) { + const del = Roles.removeUserRoles(user._id, roleName); + if (settings.get('UI_DisplayRoles') && del) { + Notifications.notifyLogged('roles-change', { + type: 'removed', + _id: roleName, + u: { + _id: user._id, + username: user.username, + }, + }); + } + } + } else { + logger.debug(`${ user.username } is in ${ roleName } group.`); + userRoles.push(roleName); + } + } + }); + } + if (_.size(userRoles)) { + return userRoles; + } + logger.debug('done with ldap group sync'); +} export function syncUserData(user, ldapUser) { @@ -179,6 +236,8 @@ export function syncUserData(user, ldapUser) { logger.debug('ldapUser', ldapUser.object); const userData = getDataToSyncUserData(ldapUser, user); + const userRoles = getRolesToSyncUserRoles(ldapUser, user); + if (user && user._id && userData) { logger.debug('setting', JSON.stringify(userData, null, 2)); if (userData.name) { @@ -197,6 +256,23 @@ export function syncUserData(user, ldapUser) { } } + if (settings.get('LDAP_Sync_User_Data_Groups') === true) { + _.each(userRoles, function(roleName) { + const add = Roles.addUserRoles(user._id, roleName); + if (settings.get('UI_DisplayRoles') && add) { + Notifications.notifyLogged('roles-change', { + type: 'removed', + _id: roleName, + u: { + _id: user._id, + username: user.username, + }, + }); + } + logger.info('Synced user group', roleName, 'from LDAP for', user.username); + }); + } + if (user && user._id && settings.get('LDAP_Sync_User_Avatar') === true) { const avatar = ldapUser._raw.thumbnailPhoto || ldapUser._raw.jpegPhoto; if (avatar) { @@ -245,7 +321,7 @@ export function addLdapUser(ldapUser, username, password) { } else if (settings.get('LDAP_Default_Domain') !== '') { userObject.email = `${ username || uniqueId.value }@${ settings.get('LDAP_Default_Domain') }`; } else { - const error = new Meteor.Error('LDAP-login-error', 'LDAP Authentication succeded, there is no email to create an account. Have you tried setting your Default Domain in LDAP Settings?'); + const error = new Meteor.Error('LDAP-login-error', 'LDAP Authentication succeeded, there is no email to create an account. Have you tried setting your Default Domain in LDAP Settings?'); logger.error(error); throw error; } @@ -270,13 +346,13 @@ export function addLdapUser(ldapUser, username, password) { }; } -export function importNewUsers(ldap) { +export function importNewUsers() { if (settings.get('LDAP_Enable') !== true) { logger.error('Can\'t run LDAP Import, LDAP is disabled'); return; } - if (!ldap) { + if (!ldap.connected) { ldap = new LDAP(); ldap.connectSync(); } @@ -341,10 +417,11 @@ function sync() { return; } - const ldap = new LDAP(); - try { - ldap.connectSync(); + if (!ldap.connected) { + ldap = new LDAP(); + ldap.connectSync(); + } let users; if (settings.get('LDAP_Background_Sync_Keep_Existant_Users_Updated') === true) { @@ -352,7 +429,7 @@ function sync() { } if (settings.get('LDAP_Background_Sync_Import_New_Users') === true) { - importNewUsers(ldap); + importNewUsers(); } if (settings.get('LDAP_Background_Sync_Keep_Existant_Users_Updated') === true) { diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index be4d76aad2ee..fcd5ed592f39 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1804,6 +1804,15 @@ "LDAP_Sync_User_Data_Description": "Keep user data in sync with server on **login** or on **background sync** (eg: name, email).", "LDAP_Sync_User_Data_FieldMap": "User Data Field Map", "LDAP_Sync_User_Data_FieldMap_Description": "Configure how user account fields (like email) are populated from a record in LDAP (once found).
As an example, `{\"cn\":\"name\", \"mail\":\"email\"}` will choose a person's human readable name from the cn attribute, and their email from the mail attribute. Additionally it is possible to use variables, for example: `{ \"#{givenName} #{sn}\": \"name\", \"mail\": \"email\" }` uses a combination of the user's first name and last name for the rocket chat `name` field.
Available fields in Rocket.Chat: `name`, `email` and `customFields`.", + "LDAP_Sync_User_Data_Groups": "Sync LDAP Groups", + "LDAP_Sync_User_Data_Groups_AutoRemove": "Auto Remove User Roles", + "LDAP_Sync_User_Data_Groups_AutoRemove_Description": "WARNING: Enabling this will automatically remove users from a role if they are not assigned in LDAP! This will only remove roles automatically that are set under the user data group map below.", + "LDAP_Sync_User_Data_Groups_BaseDN": "User Role Auto Remove", + "LDAP_Sync_User_Data_Groups_BaseDN_Description": "The LDAP BaseDN used to lookup users.", + "LDAP_Sync_User_Data_Groups_Filter": "User Group Filter", + "LDAP_Sync_User_Data_Groups_Filter_Description": "The LDAP search filter used to check if a user is in a group.", + "LDAP_Sync_User_Data_GroupsMap": "User Data Group Map", + "LDAP_Sync_User_Data_GroupsMap_Description": "Map LDAP groups to Rocket.Chat user roles
As an example, `{\"rocket-admin\":\"admin\", \"tech-support\":\"support\"}` will map the rocket-admin LDAP group to Rocket's \"admin\" role.", "LDAP_Test_Connection": "Test Connection", "LDAP_Timeout": "Timeout (ms)", "LDAP_Timeout_Description": "How many mileseconds wait for a search result before return an error", From 58ffc4a635947497222a33809c0c34e7d808cf03 Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Sun, 28 Apr 2019 02:56:40 -0400 Subject: [PATCH 02/16] i18n whoopsie --- packages/rocketchat-i18n/i18n/en.i18n.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 5f249514c078..95cb54fdbede 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1813,7 +1813,7 @@ "LDAP_Sync_User_Data_Groups": "Sync LDAP Groups", "LDAP_Sync_User_Data_Groups_AutoRemove": "Auto Remove User Roles", "LDAP_Sync_User_Data_Groups_AutoRemove_Description": "WARNING: Enabling this will automatically remove users from a role if they are not assigned in LDAP! This will only remove roles automatically that are set under the user data group map below.", - "LDAP_Sync_User_Data_Groups_BaseDN": "User Role Auto Remove", + "LDAP_Sync_User_Data_Groups_BaseDN": "LDAP Group BaseDN", "LDAP_Sync_User_Data_Groups_BaseDN_Description": "The LDAP BaseDN used to lookup users.", "LDAP_Sync_User_Data_Groups_Filter": "User Group Filter", "LDAP_Sync_User_Data_Groups_Filter_Description": "The LDAP search filter used to check if a user is in a group.", @@ -3248,4 +3248,4 @@ "Your_question": "Your question", "Your_server_link": "Your server link", "Your_workspace_is_ready": "Your workspace is ready to use 🎉" -} \ No newline at end of file +} From 6feaed764540330e772a3cc2c0836ba8f202ccb8 Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Sun, 28 Apr 2019 03:08:14 -0400 Subject: [PATCH 03/16] Removed a duplicated line.. whoopsie --- app/ldap/server/settings.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/ldap/server/settings.js b/app/ldap/server/settings.js index c1aa5313a285..25ad68a8aec3 100644 --- a/app/ldap/server/settings.js +++ b/app/ldap/server/settings.js @@ -90,7 +90,6 @@ settings.addGroup('LDAP', function() { this.add('LDAP_Sync_User_Data_FieldMap', '{"cn":"name", "mail":"email"}', { type: 'string', enableQuery: syncDataQuery }); this.add('LDAP_Sync_User_Data_Groups', false, { type: 'boolean', enableQuery }); - this.add('LDAP_Sync_User_Data_Groups_Filter', '(&(cn=#{groupName})(memberUid=#{username}))', { type: 'string', enableQuery: syncGroupsQuery }); this.add('LDAP_Sync_User_Data_Groups_AutoRemove', false, { type: 'boolean', enableQuery: syncGroupsQuery }); this.add('LDAP_Sync_User_Data_Groups_Filter', '(&(cn=#{groupName})(memberUid=#{username}))', { type: 'string', enableQuery: syncGroupsQuery }); this.add('LDAP_Sync_User_Data_Groups_BaseDN', '', { type: 'string', enableQuery: syncGroupsQuery }); From 770d53689a2fba4c4eaca4771eb88651eb0a0b87 Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Mon, 29 Apr 2019 03:31:54 -0400 Subject: [PATCH 04/16] Added auto channel join / leave by LDAP Sync --- app/ldap/server/settings.js | 22 ++++- app/ldap/server/sync.js | 101 +++++++++++++++++---- packages/rocketchat-i18n/i18n/en.i18n.json | 9 +- 3 files changed, 113 insertions(+), 19 deletions(-) diff --git a/app/ldap/server/settings.js b/app/ldap/server/settings.js index 25ad68a8aec3..e7f640143bc6 100644 --- a/app/ldap/server/settings.js +++ b/app/ldap/server/settings.js @@ -18,6 +18,11 @@ settings.addGroup('LDAP', function() { enableQuery, { _id: 'LDAP_Sync_User_Data_Groups', value: true }, ]; + const syncGroupsChannelsQuery = [ + enableQuery, + { _id: 'LDAP_Sync_User_Data_Groups', value: true }, + { _id: 'LDAP_Sync_User_Data_Groups_AutoChannels', value: true }, + ]; const groupFilterQuery = [ enableQuery, { _id: 'LDAP_Group_Filter_Enable', value: true }, @@ -93,7 +98,22 @@ settings.addGroup('LDAP', function() { this.add('LDAP_Sync_User_Data_Groups_AutoRemove', false, { type: 'boolean', enableQuery: syncGroupsQuery }); this.add('LDAP_Sync_User_Data_Groups_Filter', '(&(cn=#{groupName})(memberUid=#{username}))', { type: 'string', enableQuery: syncGroupsQuery }); this.add('LDAP_Sync_User_Data_Groups_BaseDN', '', { type: 'string', enableQuery: syncGroupsQuery }); - this.add('LDAP_Sync_User_Data_GroupsMap', '{"rocket-admin":"admin", "tech-support":"support"}', { type: 'string', enableQuery: syncGroupsQuery }); + this.add('LDAP_Sync_User_Data_GroupsMap', '{\n\t"rocket-admin": "admin",\n\t"tech-support": "support"\n}', { + type: 'code', + multiline: true, + public: false, + code: 'application/json', + enableQuery: syncGroupsQuery, + }); + this.add('LDAP_Sync_User_Data_Groups_AutoChannels', false, { type: 'boolean', enableQuery: syncGroupsQuery }); + this.add('LDAP_Sync_User_Data_Groups_AutoChannelsMap', '{\n\t"employee": "general",\n\t"techsupport": [\n\t\t"helpdesk",\n\t\t"support"\n\t]\n}', { + type: 'code', + multiline: true, + public: false, + code: 'application/json', + enableQuery: syncGroupsChannelsQuery, + }); + this.add('LDAP_Sync_User_Data_Groups_Enforce_AutoChannels', false, { type: 'boolean', enableQuery: syncGroupsChannelsQuery }); this.add('LDAP_Sync_User_Avatar', true, { type: 'boolean', enableQuery }); diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index f15aa5736fd0..3ede2e667b9c 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -4,18 +4,43 @@ import { Accounts } from 'meteor/accounts-base'; import { RocketChatFile } from '../../file'; import { settings } from '../../settings'; import { Notifications } from '../../notifications'; -import { Users, Roles } from '../../models'; +import { Users, Roles, Rooms } from '../../models'; import { Logger } from '../../logger'; import { _setRealName, _setUsername } from '../../lib'; import { templateVarHandler } from '../../utils'; import { SyncedCron } from 'meteor/littledata:synced-cron'; import { FileUpload } from '../../file-upload'; +import { addUserToRoom, removeUserFromRoom } from '../../lib/server/functions'; import _ from 'underscore'; import LDAP from './ldap'; const logger = new Logger('LDAPSync', {}); let ldap = new LDAP(); + +export function isUserInLDAPGroup(ldapUser, user, ldapGroup) { + const syncUserRolesFilter = settings.get('LDAP_Sync_User_Data_Groups_Filter').trim(); + const syncUserRolesBaseDN = settings.get('LDAP_Sync_User_Data_Groups_BaseDN').trim(); + + if (!syncUserRolesFilter || !syncUserRolesBaseDN) { + logger.error('Please setup LDAP Group Filter and LDAP Group BaseDN in LDAP Settings.'); + return false; + } + const searchOptions = { + filter: syncUserRolesFilter.replace(/#{username}/g, user.username).replace(/#{groupName}/g, ldapGroup), + scope: 'sub', + }; + + const result = ldap.searchAllSync(syncUserRolesBaseDN, searchOptions); + if (!Array.isArray(result) || result.length === 0) { + logger.debug(`${ user.username } is not in ${ ldapGroup } group!!!`); + return false; + } else { + logger.debug(`${ user.username } is in ${ ldapGroup } group.`); + return true; + } +} + export function slug(text) { if (settings.get('UTF8_Names_Slugify') !== true) { return text; @@ -172,12 +197,10 @@ export function getDataToSyncUserData(ldapUser, user) { return userData; } } -export function getRolesToSyncUserRoles(ldapUser, user) { +export function mapLdapGroupsToUserRoles(ldapUser, user) { const syncUserRoles = settings.get('LDAP_Sync_User_Data_Groups'); const syncUserRolesAutoRemove = settings.get('LDAP_Sync_User_Data_Groups_AutoRemove'); const syncUserRolesFieldMap = settings.get('LDAP_Sync_User_Data_GroupsMap').trim(); - const syncUserRolesFilter = settings.get('LDAP_Sync_User_Data_Groups_Filter').trim(); - const syncUserRolesBaseDN = settings.get('LDAP_Sync_User_Data_Groups_BaseDN').trim(); const roles = Roles.find({}, { fields: { @@ -195,14 +218,7 @@ export function getRolesToSyncUserRoles(ldapUser, user) { } else { logger.debug(`User role exists for mapping ${ roleName } -> ${ ldapField }`); - const searchOptions = { - filter: syncUserRolesFilter.replace(/#{username}/g, user.username).replace(/#{groupName}/g, ldapField), - scope: 'sub', - }; - - const result = ldap.searchAllSync(syncUserRolesBaseDN, searchOptions); - if (!Array.isArray(result) || result.length === 0) { - logger.debug(`${ user.username } is not in ${ roleName } group!!!`); + if (!isUserInLDAPGroup(ldapUser, user, ldapField)) { if (syncUserRolesAutoRemove) { const del = Roles.removeUserRoles(user._id, roleName); if (settings.get('UI_DisplayRoles') && del) { @@ -217,7 +233,6 @@ export function getRolesToSyncUserRoles(ldapUser, user) { } } } else { - logger.debug(`${ user.username } is in ${ roleName } group.`); userRoles.push(roleName); } } @@ -226,17 +241,62 @@ export function getRolesToSyncUserRoles(ldapUser, user) { if (_.size(userRoles)) { return userRoles; } - logger.debug('done with ldap group sync'); } - - +export function mapLDAPGroupsToChannels(ldapUser, user) { + const syncUserRoles = settings.get('LDAP_Sync_User_Data_Groups'); + const syncUserRolesAutoChannels = settings.get('LDAP_Sync_User_Data_Groups_AutoChannels'); + const syncUserRolesEnforceAutoChannels = settings.get('LDAP_Sync_User_Data_Groups_Enforce_AutoChannels'); + const syncUserRolesChannelFieldMap = settings.get('LDAP_Sync_User_Data_Groups_AutoChannelsMap').trim(); + + const userChannels = []; + if (syncUserRoles && syncUserRolesAutoChannels && syncUserRolesChannelFieldMap) { + const fieldMap = JSON.parse(syncUserRolesChannelFieldMap); + + _.map(fieldMap, function(channels, ldapField) { + if (typeof (channels) === 'object') { + _.each(channels, function(channel) { + const room = Rooms.findOneByName(channel); + if (!room) { + logger.error(`Channel '${ channel }' doesn't exist but is in Auto Channels map.`); + return; + } else if (isUserInLDAPGroup(ldapUser, user, ldapField)) { + userChannels.push(room._id); + } else if (syncUserRolesEnforceAutoChannels) { + removeUserFromRoom(room._id, user); + } + }); + } else { + const room = Rooms.findOneByName(channels); + if (!room) { + logger.error(`Channel '${ channels }' doesn't exist but is in Auto Channels map.`); + return; + } + if (isUserInLDAPGroup(ldapUser, user, ldapField)) { + userChannels.push(room._id); + } else if (syncUserRolesEnforceAutoChannels) { + removeUserFromRoom(room._id, user); + } + } + }); + } + if (_.size(userChannels)) { + return userChannels; + } +} export function syncUserData(user, ldapUser) { logger.info('Syncing user data'); logger.debug('user', { email: user.email, _id: user._id }); logger.debug('ldapUser', ldapUser.object); const userData = getDataToSyncUserData(ldapUser, user); - const userRoles = getRolesToSyncUserRoles(ldapUser, user); + + // Returns a list of Rocket.Chat Groups a user should belong + // to if their LDAP group matches the LDAP_Sync_User_Data_GroupsMap + const userRoles = mapLdapGroupsToUserRoles(ldapUser, user); + + // Returns a list of Rocket.Chat Channels a user should belong + // to if their LDAP group matches the LDAP_Sync_User_Data_Groups_AutoChannelsMap + const userChannels = mapLDAPGroupsToChannels(ldapUser, user); if (user && user._id && userData) { logger.debug('setting', JSON.stringify(userData, null, 2)); @@ -273,6 +333,13 @@ export function syncUserData(user, ldapUser) { }); } + if (settings.get('LDAP_Sync_User_Data_Groups_AutoChannels') === true) { + _.each(userChannels, function(userChannel) { + addUserToRoom(userChannel, user); + logger.info('Synced user channel', userChannel, 'from LDAP for', user.username); + }); + } + if (user && user._id && settings.get('LDAP_Sync_User_Avatar') === true) { const avatar = ldapUser._raw.thumbnailPhoto || ldapUser._raw.jpegPhoto; if (avatar) { diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 95cb54fdbede..c87fa866e9da 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1811,10 +1811,17 @@ "LDAP_Sync_User_Data_FieldMap": "User Data Field Map", "LDAP_Sync_User_Data_FieldMap_Description": "Configure how user account fields (like email) are populated from a record in LDAP (once found).
As an example, `{\"cn\":\"name\", \"mail\":\"email\"}` will choose a person's human readable name from the cn attribute, and their email from the mail attribute. Additionally it is possible to use variables, for example: `{ \"#{givenName} #{sn}\": \"name\", \"mail\": \"email\" }` uses a combination of the user's first name and last name for the rocket chat `name` field.
Available fields in Rocket.Chat: `name`, `email` and `customFields`.", "LDAP_Sync_User_Data_Groups": "Sync LDAP Groups", + "LDAP_Sync_User_Data_Groups_AutoChannels": "Auto Sync LDAP Groups to Channels", + "LDAP_Sync_User_Data_Groups_AutoChannels_Description": "Enable this feature to automatically add users from a channel based on their LDAP group. If you would like to also remove users from a channel, see the option below about auto removing users.", + "LDAP_Sync_User_Data_Groups_AutoChannelsMap": "LDAP Group Channel Map", + "LDAP_Sync_User_Data_Groups_AutoChannelsMap_Default": "// Enable Auto Sync LDAP Groups to Channels above", + "LDAP_Sync_User_Data_Groups_AutoChannelsMap_Description": "Map LDAP groups to Rocket.Chat channels.
As an example, `{\"employee\":\"general\"}` will add any user in the LDAP group employee, to the general channel.", "LDAP_Sync_User_Data_Groups_AutoRemove": "Auto Remove User Roles", - "LDAP_Sync_User_Data_Groups_AutoRemove_Description": "WARNING: Enabling this will automatically remove users from a role if they are not assigned in LDAP! This will only remove roles automatically that are set under the user data group map below.", + "LDAP_Sync_User_Data_Groups_AutoRemove_Description": "**Attention**: Enabling this will automatically remove users from a role if they are not assigned in LDAP! This will only remove roles automatically that are set under the user data group map below.", "LDAP_Sync_User_Data_Groups_BaseDN": "LDAP Group BaseDN", "LDAP_Sync_User_Data_Groups_BaseDN_Description": "The LDAP BaseDN used to lookup users.", + "LDAP_Sync_User_Data_Groups_Enforce_AutoChannels": "Auto Remove Users from Channels", + "LDAP_Sync_User_Data_Groups_Enforce_AutoChannels_Description": "**Attention**: Enabling this will remove any users in a channel that do not have the coorosponding LDAP group! Only enable this if you know what you're doing.", "LDAP_Sync_User_Data_Groups_Filter": "User Group Filter", "LDAP_Sync_User_Data_Groups_Filter_Description": "The LDAP search filter used to check if a user is in a group.", "LDAP_Sync_User_Data_GroupsMap": "User Data Group Map", From 7c82314d5a02a8444f58a53a73ac1a05babdf63e Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Mon, 29 Apr 2019 04:07:39 -0400 Subject: [PATCH 05/16] i18n whoopsie --- packages/rocketchat-i18n/i18n/en.i18n.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index c87fa866e9da..f76bc5e3920e 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1812,7 +1812,7 @@ "LDAP_Sync_User_Data_FieldMap_Description": "Configure how user account fields (like email) are populated from a record in LDAP (once found).
As an example, `{\"cn\":\"name\", \"mail\":\"email\"}` will choose a person's human readable name from the cn attribute, and their email from the mail attribute. Additionally it is possible to use variables, for example: `{ \"#{givenName} #{sn}\": \"name\", \"mail\": \"email\" }` uses a combination of the user's first name and last name for the rocket chat `name` field.
Available fields in Rocket.Chat: `name`, `email` and `customFields`.", "LDAP_Sync_User_Data_Groups": "Sync LDAP Groups", "LDAP_Sync_User_Data_Groups_AutoChannels": "Auto Sync LDAP Groups to Channels", - "LDAP_Sync_User_Data_Groups_AutoChannels_Description": "Enable this feature to automatically add users from a channel based on their LDAP group. If you would like to also remove users from a channel, see the option below about auto removing users.", + "LDAP_Sync_User_Data_Groups_AutoChannels_Description": "Enable this feature to automatically add users to a channel based on their LDAP group. If you would like to also remove users from a channel, see the option below about auto removing users.", "LDAP_Sync_User_Data_Groups_AutoChannelsMap": "LDAP Group Channel Map", "LDAP_Sync_User_Data_Groups_AutoChannelsMap_Default": "// Enable Auto Sync LDAP Groups to Channels above", "LDAP_Sync_User_Data_Groups_AutoChannelsMap_Description": "Map LDAP groups to Rocket.Chat channels.
As an example, `{\"employee\":\"general\"}` will add any user in the LDAP group employee, to the general channel.", From deac02c338a0d7c1492725eafb084b8e23698f50 Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Thu, 2 May 2019 01:22:17 -0400 Subject: [PATCH 06/16] Only run remove code if the user is actually in the room --- app/ldap/server/sync.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index 3ede2e667b9c..e4ffc3540bb9 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -262,7 +262,10 @@ export function mapLDAPGroupsToChannels(ldapUser, user) { } else if (isUserInLDAPGroup(ldapUser, user, ldapField)) { userChannels.push(room._id); } else if (syncUserRolesEnforceAutoChannels) { - removeUserFromRoom(room._id, user); + const subscription = Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); + if (subscription) { + removeUserFromRoom(room._id, user); + } } }); } else { @@ -274,7 +277,10 @@ export function mapLDAPGroupsToChannels(ldapUser, user) { if (isUserInLDAPGroup(ldapUser, user, ldapField)) { userChannels.push(room._id); } else if (syncUserRolesEnforceAutoChannels) { - removeUserFromRoom(room._id, user); + const subscription = Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); + if (subscription) { + removeUserFromRoom(room._id, user); + } } } }); From f9c2061c6b93a0588f541529f3f075917d850bdc Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Thu, 2 May 2019 01:30:51 -0400 Subject: [PATCH 07/16] Goes with last commit... --- app/ldap/server/sync.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index e4ffc3540bb9..48894c0c1f47 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -4,7 +4,7 @@ import { Accounts } from 'meteor/accounts-base'; import { RocketChatFile } from '../../file'; import { settings } from '../../settings'; import { Notifications } from '../../notifications'; -import { Users, Roles, Rooms } from '../../models'; +import { Users, Roles, Rooms, Subscriptions } from '../../models'; import { Logger } from '../../logger'; import { _setRealName, _setUsername } from '../../lib'; import { templateVarHandler } from '../../utils'; From b7165a003bf69536b5826b278915dc71659c1828 Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Mon, 3 Jun 2019 02:29:14 -0400 Subject: [PATCH 08/16] Fixed build + added auto channel creation --- app/ldap/server/settings.js | 1 + app/ldap/server/sync.js | 33 ++++++++++++++-------- packages/rocketchat-i18n/i18n/en.i18n.json | 2 ++ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/ldap/server/settings.js b/app/ldap/server/settings.js index dcaff9c6f0b3..1791296fc7e1 100644 --- a/app/ldap/server/settings.js +++ b/app/ldap/server/settings.js @@ -106,6 +106,7 @@ settings.addGroup('LDAP', function() { enableQuery: syncGroupsQuery, }); this.add('LDAP_Sync_User_Data_Groups_AutoChannels', false, { type: 'boolean', enableQuery: syncGroupsQuery }); + this.add('LDAP_Sync_User_Data_Groups_AutoChannels_Admin', 'rocket.cat', { type: 'string', enableQuery: syncGroupsQuery }); this.add('LDAP_Sync_User_Data_Groups_AutoChannelsMap', '{\n\t"employee": "general",\n\t"techsupport": [\n\t\t"helpdesk",\n\t\t"support"\n\t]\n}', { type: 'code', multiline: true, diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index 9b7785f5c839..e78a4631b3c4 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -13,8 +13,8 @@ import { Logger } from '../../logger'; import { _setRealName, _setUsername } from '../../lib'; import { templateVarHandler } from '../../utils'; import { FileUpload } from '../../file-upload'; -import { addUserToRoom, removeUserFromRoom } from '../../lib/server/functions'; -import _ from 'underscore'; +import { addUserToRoom, removeUserFromRoom, createRoom } from '../../lib/server/functions'; + const logger = new Logger('LDAPSync', {}); let ldap = new LDAP(); @@ -36,11 +36,11 @@ export function isUserInLDAPGroup(ldapUser, user, ldapGroup) { const result = ldap.searchAllSync(syncUserRolesBaseDN, searchOptions); if (!Array.isArray(result) || result.length === 0) { logger.debug(`${ user.username } is not in ${ ldapGroup } group!!!`); - return false; } else { logger.debug(`${ user.username } is in ${ ldapGroup } group.`); return true; } + return false; } export function slug(text) { @@ -219,7 +219,6 @@ export function mapLdapGroupsToUserRoles(ldapUser, user) { const [roleName] = userField.split(/\.(.+)/); if (!_.find(roles, (el) => el._id === roleName)) { logger.debug(`User Role doesn't exist: ${ roleName }`); - return; } else { logger.debug(`User role exists for mapping ${ roleName } -> ${ ldapField }`); @@ -247,6 +246,17 @@ export function mapLdapGroupsToUserRoles(ldapUser, user) { return userRoles; } } +export function createRoomForSync(channel) { + logger.info(`Channel '${ channel }' doesn't exist, creating it.`); + + const room = createRoom('c', channel, settings.get('LDAP_Sync_User_Data_Groups_AutoChannels_Admin'), [], false, { customFields: { ldap: true } }); + if (!room || !room.rid) { + logger.error(`Unable to auto-create channel '${ channel }' during ldap sync.`); + return; + } + room._id = room.rid; + return room; +} export function mapLDAPGroupsToChannels(ldapUser, user) { const syncUserRoles = settings.get('LDAP_Sync_User_Data_Groups'); const syncUserRolesAutoChannels = settings.get('LDAP_Sync_User_Data_Groups_AutoChannels'); @@ -258,13 +268,13 @@ export function mapLDAPGroupsToChannels(ldapUser, user) { const fieldMap = JSON.parse(syncUserRolesChannelFieldMap); _.map(fieldMap, function(channels, ldapField) { - if (typeof (channels) === 'object') { + if (typeof channels === 'object') { _.each(channels, function(channel) { - const room = Rooms.findOneByName(channel); + let room = Rooms.findOneByName(channel); if (!room) { - logger.error(`Channel '${ channel }' doesn't exist but is in Auto Channels map.`); - return; - } else if (isUserInLDAPGroup(ldapUser, user, ldapField)) { + room = createRoomForSync(channel); + } + if (isUserInLDAPGroup(ldapUser, user, ldapField)) { userChannels.push(room._id); } else if (syncUserRolesEnforceAutoChannels) { const subscription = Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); @@ -274,10 +284,9 @@ export function mapLDAPGroupsToChannels(ldapUser, user) { } }); } else { - const room = Rooms.findOneByName(channels); + let room = Rooms.findOneByName(channels); if (!room) { - logger.error(`Channel '${ channels }' doesn't exist but is in Auto Channels map.`); - return; + room = createRoomForSync(channels); } if (isUserInLDAPGroup(ldapUser, user, ldapField)) { userChannels.push(room._id); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 32c7f4d29341..0c1f85d05977 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1823,6 +1823,8 @@ "LDAP_Sync_User_Data_FieldMap_Description": "Configure how user account fields (like email) are populated from a record in LDAP (once found).
As an example, `{\"cn\":\"name\", \"mail\":\"email\"}` will choose a person's human readable name from the cn attribute, and their email from the mail attribute. Additionally it is possible to use variables, for example: `{ \"#{givenName} #{sn}\": \"name\", \"mail\": \"email\" }` uses a combination of the user's first name and last name for the rocket chat `name` field.
Available fields in Rocket.Chat: `name`, `email` and `customFields`.", "LDAP_Sync_User_Data_Groups": "Sync LDAP Groups", "LDAP_Sync_User_Data_Groups_AutoChannels": "Auto Sync LDAP Groups to Channels", + "LDAP_Sync_User_Data_Groups_AutoChannels_Admin": "Channel Admin", + "LDAP_Sync_User_Data_Groups_AutoChannels_Admin_Description": "When channels are auto-created that do not exist during a sync, this user will automatically become the admin for the channel.", "LDAP_Sync_User_Data_Groups_AutoChannels_Description": "Enable this feature to automatically add users to a channel based on their LDAP group. If you would like to also remove users from a channel, see the option below about auto removing users.", "LDAP_Sync_User_Data_Groups_AutoChannelsMap": "LDAP Group Channel Map", "LDAP_Sync_User_Data_Groups_AutoChannelsMap_Default": "// Enable Auto Sync LDAP Groups to Channels above", From f53734107d265a2e7822f42dc47fa968106163d9 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 20 Aug 2019 13:32:08 -0300 Subject: [PATCH 09/16] fixed query used on new setting --- app/ldap/server/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ldap/server/settings.js b/app/ldap/server/settings.js index 1791296fc7e1..598537a8056e 100644 --- a/app/ldap/server/settings.js +++ b/app/ldap/server/settings.js @@ -106,7 +106,7 @@ settings.addGroup('LDAP', function() { enableQuery: syncGroupsQuery, }); this.add('LDAP_Sync_User_Data_Groups_AutoChannels', false, { type: 'boolean', enableQuery: syncGroupsQuery }); - this.add('LDAP_Sync_User_Data_Groups_AutoChannels_Admin', 'rocket.cat', { type: 'string', enableQuery: syncGroupsQuery }); + this.add('LDAP_Sync_User_Data_Groups_AutoChannels_Admin', 'rocket.cat', { type: 'string', enableQuery: syncGroupsChannelsQuery }); this.add('LDAP_Sync_User_Data_Groups_AutoChannelsMap', '{\n\t"employee": "general",\n\t"techsupport": [\n\t\t"helpdesk",\n\t\t"support"\n\t]\n}', { type: 'code', multiline: true, From 4e22be20744e14f2f73e8d200e1942c4e38e820f Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 20 Aug 2019 13:32:48 -0300 Subject: [PATCH 10/16] Fixed notification type --- app/ldap/server/sync.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index c756b78bcf4a..98d8243af3a0 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -341,7 +341,7 @@ export function syncUserData(user, ldapUser) { const add = Roles.addUserRoles(user._id, roleName); if (settings.get('UI_DisplayRoles') && add) { Notifications.notifyLogged('roles-change', { - type: 'removed', + type: 'added', _id: roleName, u: { _id: user._id, From 87f52773454519aa89f5c0ab75a2acf53d1b8e98 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 20 Aug 2019 13:33:27 -0300 Subject: [PATCH 11/16] Fixed indentation and other styling rules --- app/ldap/server/sync.js | 139 +++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 67 deletions(-) diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index 98d8243af3a0..eac20cc0bc97 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -212,39 +212,46 @@ export function mapLdapGroupsToUserRoles(ldapUser, user) { _updatedAt: 0, }, }).fetch(); - const userRoles = []; - if (syncUserRoles && syncUserRolesFieldMap) { - const fieldMap = JSON.parse(syncUserRolesFieldMap); - _.map(fieldMap, function(userField, ldapField) { - const [roleName] = userField.split(/\.(.+)/); - if (!_.find(roles, (el) => el._id === roleName)) { - logger.debug(`User Role doesn't exist: ${ roleName }`); - } else { - logger.debug(`User role exists for mapping ${ roleName } -> ${ ldapField }`); - - if (!isUserInLDAPGroup(ldapUser, user, ldapField)) { - if (syncUserRolesAutoRemove) { - const del = Roles.removeUserRoles(user._id, roleName); - if (settings.get('UI_DisplayRoles') && del) { - Notifications.notifyLogged('roles-change', { - type: 'removed', - _id: roleName, - u: { - _id: user._id, - username: user.username, - }, - }); - } - } - } else { - userRoles.push(roleName); - } - } - }); - } - if (_.size(userRoles)) { - return userRoles; + + if (!syncUserRoles || !syncUserRolesFieldMap) { + return []; } + + const userRoles = []; + + const fieldMap = JSON.parse(syncUserRolesFieldMap); + _.map(fieldMap, function(userField, ldapField) { + const [roleName] = userField.split(/\.(.+)/); + if (!_.find(roles, (el) => el._id === roleName)) { + logger.debug(`User Role doesn't exist: ${ roleName }`); + return; + } + + logger.debug(`User role exists for mapping ${ roleName } -> ${ ldapField }`); + + if (isUserInLDAPGroup(ldapUser, user, ldapField)) { + userRoles.push(roleName); + return; + } + + if (!syncUserRolesAutoRemove) { + return; + } + + const del = Roles.removeUserRoles(user._id, roleName); + if (settings.get('UI_DisplayRoles') && del) { + Notifications.notifyLogged('roles-change', { + type: 'removed', + _id: roleName, + u: { + _id: user._id, + username: user.username, + }, + }); + } + }); + + return userRoles; } export function createRoomForSync(channel) { logger.info(`Channel '${ channel }' doesn't exist, creating it.`); @@ -257,6 +264,7 @@ export function createRoomForSync(channel) { room._id = room.rid; return room; } + export function mapLDAPGroupsToChannels(ldapUser, user) { const syncUserRoles = settings.get('LDAP_Sync_User_Data_Groups'); const syncUserRolesAutoChannels = settings.get('LDAP_Sync_User_Data_Groups_AutoChannels'); @@ -264,45 +272,42 @@ export function mapLDAPGroupsToChannels(ldapUser, user) { const syncUserRolesChannelFieldMap = settings.get('LDAP_Sync_User_Data_Groups_AutoChannelsMap').trim(); const userChannels = []; - if (syncUserRoles && syncUserRolesAutoChannels && syncUserRolesChannelFieldMap) { - const fieldMap = JSON.parse(syncUserRolesChannelFieldMap); - - _.map(fieldMap, function(channels, ldapField) { - if (typeof channels === 'object') { - _.each(channels, function(channel) { - let room = Rooms.findOneByName(channel); - if (!room) { - room = createRoomForSync(channel); - } - if (isUserInLDAPGroup(ldapUser, user, ldapField)) { - userChannels.push(room._id); - } else if (syncUserRolesEnforceAutoChannels) { - const subscription = Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); - if (subscription) { - removeUserFromRoom(room._id, user); - } - } - }); - } else { - let room = Rooms.findOneByName(channels); - if (!room) { - room = createRoomForSync(channels); - } - if (isUserInLDAPGroup(ldapUser, user, ldapField)) { - userChannels.push(room._id); - } else if (syncUserRolesEnforceAutoChannels) { - const subscription = Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); - if (subscription) { - removeUserFromRoom(room._id, user); - } + if (!syncUserRoles || !syncUserRolesAutoChannels || !syncUserRolesChannelFieldMap) { + return []; + } + + let fieldMap; + try { + fieldMap = JSON.parse(syncUserRolesChannelFieldMap); + } catch (err) { + logger.error(`Unexpected error : ${ err.message }`); + return []; + } + + _.map(fieldMap, function(channels, ldapField) { + if (!Array.isArray(channels)) { + channels = [channels]; + } + + _.each(channels, function(channel) { + let room = Rooms.findOneByName(channel); + if (!room) { + room = createRoomForSync(channel); + } + if (isUserInLDAPGroup(ldapUser, user, ldapField)) { + userChannels.push(room._id); + } else if (syncUserRolesEnforceAutoChannels) { + const subscription = Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); + if (subscription) { + removeUserFromRoom(room._id, user); } } }); - } - if (_.size(userChannels)) { - return userChannels; - } + }); + + return userChannels; } + export function syncUserData(user, ldapUser) { logger.info('Syncing user data'); logger.debug('user', { email: user.email, _id: user._id }); From 3f75c5023b9114e522c3e8ff3e2ef70910618c33 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 20 Aug 2019 14:42:50 -0300 Subject: [PATCH 12/16] Removed unnecessary underscore calls --- app/ldap/server/sync.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index eac20cc0bc97..dcce503e5c3e 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -289,7 +289,7 @@ export function mapLDAPGroupsToChannels(ldapUser, user) { channels = [channels]; } - _.each(channels, function(channel) { + for (const channel of channels) { let room = Rooms.findOneByName(channel); if (!room) { room = createRoomForSync(channel); @@ -302,7 +302,7 @@ export function mapLDAPGroupsToChannels(ldapUser, user) { removeUserFromRoom(room._id, user); } } - }); + } }); return userChannels; @@ -342,7 +342,7 @@ export function syncUserData(user, ldapUser) { } if (settings.get('LDAP_Sync_User_Data_Groups') === true) { - _.each(userRoles, function(roleName) { + for (const roleName of userRoles) { const add = Roles.addUserRoles(user._id, roleName); if (settings.get('UI_DisplayRoles') && add) { Notifications.notifyLogged('roles-change', { @@ -355,14 +355,14 @@ export function syncUserData(user, ldapUser) { }); } logger.info('Synced user group', roleName, 'from LDAP for', user.username); - }); + } } if (settings.get('LDAP_Sync_User_Data_Groups_AutoChannels') === true) { - _.each(userChannels, function(userChannel) { + for (const userChannel of userChannels) { addUserToRoom(userChannel, user); logger.info('Synced user channel', userChannel, 'from LDAP for', user.username); - }); + } } if (user && user._id && settings.get('LDAP_Sync_User_Avatar') === true) { From feb4603bb0e67bc92d10bc3f9b3954789a26cf97 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 20 Aug 2019 17:23:28 -0300 Subject: [PATCH 13/16] use a local Ldap instance for all connections --- app/ldap/server/sync.js | 44 ++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index dcce503e5c3e..b144cd5b244d 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -17,10 +17,8 @@ import { addUserToRoom, removeUserFromRoom, createRoom } from '../../lib/server/ const logger = new Logger('LDAPSync', {}); -let ldap = new LDAP(); - -export function isUserInLDAPGroup(ldapUser, user, ldapGroup) { +export function isUserInLDAPGroup(ldap, ldapUser, user, ldapGroup) { const syncUserRolesFilter = settings.get('LDAP_Sync_User_Data_Groups_Filter').trim(); const syncUserRolesBaseDN = settings.get('LDAP_Sync_User_Data_Groups_BaseDN').trim(); @@ -202,7 +200,7 @@ export function getDataToSyncUserData(ldapUser, user) { return userData; } } -export function mapLdapGroupsToUserRoles(ldapUser, user) { +export function mapLdapGroupsToUserRoles(ldap, ldapUser, user) { const syncUserRoles = settings.get('LDAP_Sync_User_Data_Groups'); const syncUserRolesAutoRemove = settings.get('LDAP_Sync_User_Data_Groups_AutoRemove'); const syncUserRolesFieldMap = settings.get('LDAP_Sync_User_Data_GroupsMap').trim(); @@ -229,7 +227,7 @@ export function mapLdapGroupsToUserRoles(ldapUser, user) { logger.debug(`User role exists for mapping ${ roleName } -> ${ ldapField }`); - if (isUserInLDAPGroup(ldapUser, user, ldapField)) { + if (isUserInLDAPGroup(ldap, ldapUser, user, ldapField)) { userRoles.push(roleName); return; } @@ -265,7 +263,7 @@ export function createRoomForSync(channel) { return room; } -export function mapLDAPGroupsToChannels(ldapUser, user) { +export function mapLDAPGroupsToChannels(ldap, ldapUser, user) { const syncUserRoles = settings.get('LDAP_Sync_User_Data_Groups'); const syncUserRolesAutoChannels = settings.get('LDAP_Sync_User_Data_Groups_AutoChannels'); const syncUserRolesEnforceAutoChannels = settings.get('LDAP_Sync_User_Data_Groups_Enforce_AutoChannels'); @@ -294,7 +292,7 @@ export function mapLDAPGroupsToChannels(ldapUser, user) { if (!room) { room = createRoomForSync(channel); } - if (isUserInLDAPGroup(ldapUser, user, ldapField)) { + if (isUserInLDAPGroup(ldap, ldapUser, user, ldapField)) { userChannels.push(room._id); } else if (syncUserRolesEnforceAutoChannels) { const subscription = Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); @@ -308,7 +306,7 @@ export function mapLDAPGroupsToChannels(ldapUser, user) { return userChannels; } -export function syncUserData(user, ldapUser) { +export function syncUserData(user, ldapUser, ldap) { logger.info('Syncing user data'); logger.debug('user', { email: user.email, _id: user._id }); logger.debug('ldapUser', ldapUser.object); @@ -317,11 +315,11 @@ export function syncUserData(user, ldapUser) { // Returns a list of Rocket.Chat Groups a user should belong // to if their LDAP group matches the LDAP_Sync_User_Data_GroupsMap - const userRoles = mapLdapGroupsToUserRoles(ldapUser, user); + const userRoles = mapLdapGroupsToUserRoles(ldap, ldapUser, user); // Returns a list of Rocket.Chat Channels a user should belong // to if their LDAP group matches the LDAP_Sync_User_Data_Groups_AutoChannelsMap - const userChannels = mapLDAPGroupsToChannels(ldapUser, user); + const userChannels = mapLDAPGroupsToChannels(ldap, ldapUser, user); if (user && user._id && userData) { logger.debug('setting', JSON.stringify(userData, null, 2)); @@ -391,7 +389,7 @@ export function syncUserData(user, ldapUser) { } } -export function addLdapUser(ldapUser, username, password) { +export function addLdapUser(ldapUser, username, password, ldap) { const uniqueId = getLdapUserUniqueID(ldapUser); const userObject = {}; @@ -431,21 +429,24 @@ export function addLdapUser(ldapUser, username, password) { return error; } - syncUserData(userObject, ldapUser); + syncUserData(userObject, ldapUser, ldap); return { userId: userObject._id, }; } -export function importNewUsers() { +export function importNewUsers(ldap) { if (settings.get('LDAP_Enable') !== true) { logger.error('Can\'t run LDAP Import, LDAP is disabled'); return; } - if (!ldap.connected) { + if (!ldap) { ldap = new LDAP(); + } + + if (!ldap.connected) { ldap.connectSync(); } @@ -483,12 +484,12 @@ export function importNewUsers() { user = Meteor.users.findOne(userQuery); if (user) { - syncUserData(user, ldapUser); + syncUserData(user, ldapUser, ldap); } } if (!user) { - addLdapUser(ldapUser, username); + addLdapUser(ldapUser, username, undefined, ldap); } if (count % 100 === 0) { @@ -509,11 +510,10 @@ function sync() { return; } + const ldap = new LDAP(); + try { - if (!ldap.connected) { - ldap = new LDAP(); - ldap.connectSync(); - } + ldap.connectSync(); let users; if (settings.get('LDAP_Background_Sync_Keep_Existant_Users_Updated') === true) { @@ -521,7 +521,7 @@ function sync() { } if (settings.get('LDAP_Background_Sync_Import_New_Users') === true) { - importNewUsers(); + importNewUsers(ldap); } if (settings.get('LDAP_Background_Sync_Keep_Existant_Users_Updated') === true) { @@ -535,7 +535,7 @@ function sync() { } if (ldapUser) { - syncUserData(user, ldapUser); + syncUserData(user, ldapUser, ldap); } else { logger.info('Can\'t sync user', user.username); } From c54aec118f60fdaaa3ee04924404841c57c8d64a Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 20 Aug 2019 17:50:56 -0300 Subject: [PATCH 14/16] Use try...catch to handle unexpected exceptions --- app/ldap/server/sync.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index b144cd5b244d..a576d97e9784 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -215,9 +215,16 @@ export function mapLdapGroupsToUserRoles(ldap, ldapUser, user) { return []; } - const userRoles = []; + let fieldMap; - const fieldMap = JSON.parse(syncUserRolesFieldMap); + try { + fieldMap = JSON.parse(syncUserRolesFieldMap); + } catch (err) { + logger.error(`Unexpected error : ${ err.message }`); + return []; + } + + const userRoles = []; _.map(fieldMap, function(userField, ldapField) { const [roleName] = userField.split(/\.(.+)/); if (!_.find(roles, (el) => el._id === roleName)) { From ebbc33f27e53cd1849b5d8d7329a933079df498c Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 20 Aug 2019 19:36:19 -0300 Subject: [PATCH 15/16] Code formatting --- app/ldap/server/sync.js | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index a576d97e9784..2eb6ebd334aa 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -38,6 +38,7 @@ export function isUserInLDAPGroup(ldap, ldapUser, user, ldapGroup) { logger.debug(`${ user.username } is in ${ ldapGroup } group.`); return true; } + return false; } @@ -205,13 +206,17 @@ export function mapLdapGroupsToUserRoles(ldap, ldapUser, user) { const syncUserRolesAutoRemove = settings.get('LDAP_Sync_User_Data_Groups_AutoRemove'); const syncUserRolesFieldMap = settings.get('LDAP_Sync_User_Data_GroupsMap').trim(); + if (!syncUserRoles || !syncUserRolesFieldMap) { + return []; + } + const roles = Roles.find({}, { fields: { _updatedAt: 0, }, }).fetch(); - if (!syncUserRoles || !syncUserRolesFieldMap) { + if (!roles) { return []; } @@ -223,24 +228,34 @@ export function mapLdapGroupsToUserRoles(ldap, ldapUser, user) { logger.error(`Unexpected error : ${ err.message }`); return []; } + if (!fieldMap) { + return []; + } const userRoles = []; - _.map(fieldMap, function(userField, ldapField) { + + for (const ldapField in fieldMap) { + if (!fieldMap.hasOwnProperty(ldapField)) { + continue; + } + + const userField = fieldMap[ldapField]; + const [roleName] = userField.split(/\.(.+)/); if (!_.find(roles, (el) => el._id === roleName)) { logger.debug(`User Role doesn't exist: ${ roleName }`); - return; + continue; } - logger.debug(`User role exists for mapping ${ roleName } -> ${ ldapField }`); + logger.debug(`User role exists for mapping ${ ldapField } -> ${ roleName }`); if (isUserInLDAPGroup(ldap, ldapUser, user, ldapField)) { userRoles.push(roleName); - return; + continue; } if (!syncUserRolesAutoRemove) { - return; + continue; } const del = Roles.removeUserRoles(user._id, roleName); @@ -254,7 +269,7 @@ export function mapLdapGroupsToUserRoles(ldap, ldapUser, user) { }, }); } - }); + } return userRoles; } @@ -289,6 +304,10 @@ export function mapLDAPGroupsToChannels(ldap, ldapUser, user) { return []; } + if (!fieldMap) { + return []; + } + _.map(fieldMap, function(channels, ldapField) { if (!Array.isArray(channels)) { channels = [channels]; From 23b043c4947c050ef78bff0323fa0fcfe449c6de Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 20 Aug 2019 22:24:47 -0300 Subject: [PATCH 16/16] Reuse ldap connection --- app/ldap/server/loginHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ldap/server/loginHandler.js b/app/ldap/server/loginHandler.js index 6fab60145417..d8ec65ce7a88 100644 --- a/app/ldap/server/loginHandler.js +++ b/app/ldap/server/loginHandler.js @@ -125,7 +125,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) { logger.info('Logging user'); - syncUserData(user, ldapUser); + syncUserData(user, ldapUser, ldap); if (settings.get('LDAP_Login_Fallback') === true && typeof loginRequest.ldapPass === 'string' && loginRequest.ldapPass.trim() !== '') { Accounts.setPassword(user._id, loginRequest.ldapPass, { logout: false });