Skip to content

Commit

Permalink
[FIX] Advanced LDAP Sync Features (#23608)
Browse files Browse the repository at this point in the history
* [FIX] Advanced LDAP Sync Features

* lint fixes

* Update ee/server/settings/ldap.ts

Co-authored-by: Diego Sampaio <chinello@gmail.com>

* Update ee/server/settings/ldap.ts

Co-authored-by: Diego Sampaio <chinello@gmail.com>

* Removed unnecessary try..finally

Co-authored-by: Diego Sampaio <chinello@gmail.com>
  • Loading branch information
pierre-lehnen-rc and sampaiodiego authored Nov 5, 2021
1 parent 9726f44 commit 21f69ac
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 27 deletions.
19 changes: 16 additions & 3 deletions app/importer/server/classes/ImportDataConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,18 @@ export class ImportDataConverter {

userData._id = _id;

if (!userData.roles && !existingUser.roles) {
userData.roles = ['user'];
}
if (!userData.type && !existingUser.type) {
userData.type = 'user';
}

// #ToDo: #TODO: Move this to the model class
const updateData: Record<string, any> = {
$set: {
roles: userData.roles || ['user'],
type: userData.type || 'user',
...userData.roles && { roles: userData.roles },
...userData.type && { type: userData.type },
...userData.statusText && { statusText: userData.statusText },
...userData.bio && { bio: userData.bio },
...userData.services?.ldap && { ldap: true },
Expand All @@ -235,7 +242,13 @@ export class ImportDataConverter {
this.addUserServices(updateData, userData);
this.addUserImportId(updateData, userData);
this.addUserEmails(updateData, userData, existingUser.emails || []);
Users.update({ _id }, updateData);

if (Object.keys(updateData.$set).length === 0) {
delete updateData.$set;
}
if (Object.keys(updateData).length > 0) {
Users.update({ _id }, updateData);
}

if (userData.utcOffset) {
Users.setUtcOffset(_id, userData.utcOffset);
Expand Down
53 changes: 35 additions & 18 deletions ee/server/lib/ldap/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,13 @@ export class LDAPEEManager extends LDAPManager {
try {
await ldap.connect();

try {
const createNewUsers = settings.get<boolean>('LDAP_Background_Sync_Import_New_Users') ?? true;
const updateExistingUsers = settings.get<boolean>('LDAP_Background_Sync_Keep_Existant_Users_Updated') ?? true;
const createNewUsers = settings.get<boolean>('LDAP_Background_Sync_Import_New_Users') ?? true;
const updateExistingUsers = settings.get<boolean>('LDAP_Background_Sync_Keep_Existant_Users_Updated') ?? true;

if (createNewUsers) {
await this.importNewUsers(ldap, converter, updateExistingUsers);
} else if (updateExistingUsers) {
await this.updateExistingUsers(ldap, converter);
}
} finally {
ldap.disconnect();
if (createNewUsers) {
await this.importNewUsers(ldap, converter, updateExistingUsers);
} else if (updateExistingUsers) {
await this.updateExistingUsers(ldap, converter);
}

converter.convertUsers({
Expand All @@ -55,6 +51,8 @@ export class LDAPEEManager extends LDAPManager {
} catch (error) {
logger.error(error);
}

ldap.disconnect();
}

public static async syncAvatars(): Promise<void> {
Expand Down Expand Up @@ -116,12 +114,12 @@ export class LDAPEEManager extends LDAPManager {
public static async advancedSyncForUser(ldap: LDAPConnection, user: IUser, isNewRecord: boolean, dn: string): Promise<void> {
await this.syncUserRoles(ldap, user, dn);
await this.syncUserChannels(ldap, user, dn);
await this.syncUserTeams(ldap, user, isNewRecord);
await this.syncUserTeams(ldap, user, dn, isNewRecord);
}

private static async advancedSync(ldap: LDAPConnection, importUser: IImportUser, converter: LDAPDataConverter, isNewRecord: boolean): Promise<void> {
const user = converter.findExistingUser(importUser);
if (!user || user.username) {
if (!user?.username) {
return;
}

Expand All @@ -140,6 +138,7 @@ export class LDAPEEManager extends LDAPManager {
};

const result = await ldap.searchRaw(baseDN, searchOptions);

if (!Array.isArray(result) || result.length === 0) {
logger.debug(`${ username } is not in ${ groupName } group!!!`);
} else {
Expand Down Expand Up @@ -314,7 +313,7 @@ export class LDAPEEManager extends LDAPManager {
}
}

private static async syncUserTeams(ldap: LDAPConnection, user: IUser, isNewRecord: boolean): Promise<void> {
private static async syncUserTeams(ldap: LDAPConnection, user: IUser, dn: string, isNewRecord: boolean): Promise<void> {
if (!user.username) {
return;
}
Expand All @@ -324,7 +323,7 @@ export class LDAPEEManager extends LDAPManager {
return;
}

const ldapUserTeams = await this.getLdapTeamsByUsername(ldap, user.username);
const ldapUserTeams = await this.getLdapTeamsByUsername(ldap, user.username, dn);
const mapJson = settings.get<string>('LDAP_Groups_To_Rocket_Chat_Teams');
if (!mapJson) {
return;
Expand Down Expand Up @@ -369,24 +368,42 @@ export class LDAPEEManager extends LDAPManager {
return [...new Set(filteredTeams.map((ldapTeam) => mappedTeams[ldapTeam]).flat())];
}

private static async getLdapTeamsByUsername(ldap: LDAPConnection, username: string): Promise<Array<string>> {
private static async getLdapTeamsByUsername(ldap: LDAPConnection, username: string, dn: string): Promise<Array<string>> {
const baseDN = (settings.get<string>('LDAP_Teams_BaseDN') ?? '').trim() || ldap.options.baseDN;
const query = settings.get<string>('LDAP_Query_To_Get_User_Teams');
if (!query) {
return [];
}

const searchOptions = {
filter: query.replace(/#{username}/g, username),
filter: query.replace(/#{username}/g, username).replace(/#{userdn}/g, dn),
scope: ldap.options.userSearchScope || 'sub',
sizeLimit: ldap.options.searchSizeLimit,
};

const ldapUserGroups = await ldap.searchRaw(ldap.options.baseDN, searchOptions);
const attributeNames = (settings.get<string>('LDAP_Teams_Name_Field') ?? '').split(',').map((attributeName) => attributeName.trim());
if (!attributeNames.length) {
attributeNames.push('ou');
}

const ldapUserGroups = await ldap.searchRaw(baseDN, searchOptions);
if (!Array.isArray(ldapUserGroups)) {
return [];
}

return ldapUserGroups.filter((entry) => entry?.raw?.ou).map((entry) => (ldap.extractLdapAttribute(entry.raw.ou) as string)).flat();
return ldapUserGroups.map((entry) => {
if (!entry?.raw) {
return undefined;
}

for (const attributeName of attributeNames) {
if (entry.raw[attributeName]) {
return ldap.extractLdapAttribute(entry.raw[attributeName]) as string;
}
}

return undefined;
}).filter((entry): entry is string => Boolean(entry)).flat();
}

private static isUserDeactivated(ldapUser: ILDAPEntry): boolean {
Expand Down
24 changes: 19 additions & 5 deletions ee/server/settings/ldap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,20 +200,34 @@ export function addSettings(): void {
enableQuery: { _id: 'LDAP_Enable', value: true },
invalidValue: false,
});

const enableQueryTeams = { _id: 'LDAP_Enable_LDAP_Groups_To_RC_Teams', value: true };

this.add('LDAP_Groups_To_Rocket_Chat_Teams', '{}', {
type: 'code',
enableQuery: { _id: 'LDAP_Enable_LDAP_Groups_To_RC_Teams', value: true },
enableQuery: enableQueryTeams,
invalidValue: '{}',
});
this.add('LDAP_Validate_Teams_For_Each_Login', false, {
type: 'boolean',
enableQuery: { _id: 'LDAP_Enable_LDAP_Groups_To_RC_Teams', value: true },
enableQuery: enableQueryTeams,
invalidValue: false,
});
this.add('LDAP_Query_To_Get_User_Teams', '(&(ou=*)(uniqueMember=uid=#{username},dc=example,dc=com))', {
this.add('LDAP_Teams_BaseDN', '', {
type: 'string',
enableQuery: enableQueryTeams,
invalidValue: '',
});
this.add('LDAP_Teams_Name_Field', 'ou,cn', {
type: 'string',
enableQuery: { _id: 'LDAP_Enable_LDAP_Groups_To_RC_Teams', value: true },
invalidValue: '(&(ou=*)(uniqueMember=uid=#{username},dc=example,dc=com))',
enableQuery: enableQueryTeams,
invalidValue: '',
});

this.add('LDAP_Query_To_Get_User_Teams', '(&(ou=*)(uniqueMember=#{userdn}))', {
type: 'string',
enableQuery: enableQueryTeams,
invalidValue: '',
});
});
});
Expand Down
4 changes: 4 additions & 0 deletions packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -2552,6 +2552,10 @@
"LDAP_Sync_User_Data_Roles_Filter_Description": "The LDAP search filter used to check if a user is in a group.",
"LDAP_Sync_User_Data_RolesMap": "User Data Group Map",
"LDAP_Sync_User_Data_RolesMap_Description": "Map LDAP groups to Rocket.Chat user roles <br/>As an example, `{\"rocket-admin\":\"admin\", \"tech-support\":\"support\"}` will map the rocket-admin LDAP group to Rocket's \"admin\" role.",
"LDAP_Teams_BaseDN": "LDAP Teams BaseDN",
"LDAP_Teams_BaseDN_Description": "The LDAP BaseDN used to lookup user teams.",
"LDAP_Teams_Name_Field": "LDAP Team Name Attribute",
"LDAP_Teams_Name_Field_Description": "The LDAP attribute that Rocket.Chat should use to load the team's name. You can specify more than one possible attribute name if you separate them with a comma.",
"LDAP_Timeout": "Timeout (ms)",
"LDAP_Timeout_Description": "How many mileseconds wait for a search result before return an error",
"LDAP_Unique_Identifier_Field": "Unique Identifier Field",
Expand Down
9 changes: 8 additions & 1 deletion server/lib/ldap/Connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,14 @@ export class LDAPConnection {
Object.keys(values._raw).forEach((key) => {
values[key] = this.extractLdapAttribute(values._raw[key]);

mapLogger.debug({ msg: 'Extracted Attribute', key, type: typeof values[key], value: values[key] });
const dataType = typeof values[key];
// eslint-disable-next-line no-control-regex
if (dataType === 'string' && values[key].length > 100 && /[\x00-\x1F]/.test(values[key])) {
mapLogger.debug({ msg: 'Extracted Attribute', key, type: dataType, length: values[key].length, value: `${ values[key].substr(0, 100) }...` });
return;
}

mapLogger.debug({ msg: 'Extracted Attribute', key, type: dataType, value: values[key] });
});

return values;
Expand Down

0 comments on commit 21f69ac

Please sign in to comment.