Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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