Skip to content
Merged
7 changes: 7 additions & 0 deletions .changeset/stupid-rabbits-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/core-typings': minor
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Adds a new setting to allow syncing federated users data through LDAP
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ export class UserConverter extends RecordConverter<IImportUserRecord, UserConver
return;
}

if (Boolean(userData.federated) !== Boolean(existingUser.federated)) {
throw new Error("Local and Federated users can't be converted to each other.");
}

userData._id = _id;

if (!userData.roles && !existingUser.roles) {
Expand Down Expand Up @@ -295,8 +299,9 @@ export class UserConverter extends RecordConverter<IImportUserRecord, UserConver
await Users.setUtcOffset(_id, userData.utcOffset);
}

if (userData.name || userData.username) {
await saveUserIdentity({ _id, name: userData.name, username: userData.username } as Parameters<typeof saveUserIdentity>[0]);
const localUsername = userData.federated ? undefined : userData.username;
if (userData.name || localUsername) {
await saveUserIdentity({ _id, name: userData.name, username: localUsername } as Parameters<typeof saveUserIdentity>[0]);
}

if (userData.importIds.length) {
Expand Down Expand Up @@ -347,6 +352,7 @@ export class UserConverter extends RecordConverter<IImportUserRecord, UserConver
...(!!userData.customFields && { customFields: userData.customFields }),
...(userData.deleted !== undefined && { active: !userData.deleted }),
...(userData.voipExtension !== undefined && { freeSwitchExtension: userData.voipExtension }),
...(userData.federated !== undefined && { federated: userData.federated }),
};
}

Expand Down
48 changes: 48 additions & 0 deletions apps/meteor/server/lib/ldap/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export class LDAPManager {
return this.fallbackToDefaultLogin(username, password);
}

const homeServer = this.getFederationHomeServer(ldapUser);
if (homeServer) {
return this.fallbackToDefaultLogin(username, password);
}

const slugifiedUsername = this.slugifyUsername(ldapUser, username);
const user = await this.findExistingUser(ldapUser, slugifiedUsername);

Expand Down Expand Up @@ -78,6 +83,11 @@ export class LDAPManager {
return;
}

const homeServer = this.getFederationHomeServer(ldapUser);
if (homeServer) {
return;
}

const slugifiedUsername = this.slugifyUsername(ldapUser, username);
const user = await this.findExistingUser(ldapUser, slugifiedUsername);

Expand Down Expand Up @@ -165,6 +175,7 @@ export class LDAPManager {

const { attribute: idAttribute, value: id } = uniqueId;
const username = this.slugifyUsername(ldapUser, usedUsername || id || '') || undefined;
const homeServer = this.getFederationHomeServer(ldapUser);
const emails = this.getLdapEmails(ldapUser, username).map((email) => email.trim());
const name = this.getLdapName(ldapUser) || undefined;
const voipExtension = this.getLdapExtension(ldapUser);
Expand All @@ -182,6 +193,10 @@ export class LDAPManager {
id,
},
},
...(homeServer && {
username: `${username}:${homeServer}`,
federated: true,
}),
};

this.onMapUserData(ldapUser, userData);
Expand Down Expand Up @@ -500,6 +515,39 @@ export class LDAPManager {
return this.getLdapDynamicValue(ldapUser, usernameField);
}

protected static getFederationHomeServer(ldapUser: ILDAPEntry): string | undefined {
if (!settings.get<boolean>('Federation_Matrix_enabled')) {
return;
}

const homeServerField = settings.get<string>('LDAP_FederationHomeServer_Field');
const homeServer = this.getLdapDynamicValue(ldapUser, homeServerField);

if (!homeServer) {
return;
}

logger.debug({ msg: 'User has a federation home server', homeServer });

const localServer = settings.get<string>('Federation_Matrix_homeserver_domain');
if (localServer === homeServer) {
return;
}

return homeServer;
}

protected static getFederatedUsername(ldapUser: ILDAPEntry, requestUsername: string): string {
const username = this.slugifyUsername(ldapUser, requestUsername);
const homeServer = this.getFederationHomeServer(ldapUser);

if (homeServer) {
return `${username}:${homeServer}`;
}

return username;
}

// This method will find existing users by LDAP id or by username.
private static async findExistingUser(ldapUser: ILDAPEntry, slugifiedUsername: string): Promise<IUser | undefined | null> {
const user = await this.findExistingLDAPUser(ldapUser);
Expand Down
5 changes: 5 additions & 0 deletions apps/meteor/server/settings/ldap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ export const createLdapSettings = () =>
type: 'string',
enableQuery,
});

await this.add('LDAP_FederationHomeServer_Field', '', {
type: 'string',
enableQuery,
});
});

await this.section('LDAP_DataSync_Avatar', async function () {
Expand Down
1 change: 1 addition & 0 deletions packages/core-typings/src/import/IImportUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export interface IImportUser {

password?: string;
voipExtension?: string;
federated?: boolean;
}
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -3014,6 +3014,8 @@
"LDAP_Enable_Description": "Attempt to utilize LDAP for authentication.",
"LDAP_Encryption": "Encryption",
"LDAP_Encryption_Description": "The encryption method used to secure communications to the LDAP server. Examples include `plain` (no encryption), `SSL/LDAPS` (encrypted from the start), and `StartTLS` (upgrade to encrypted communication once connected).",
"LDAP_FederationHomeServer_Field": "Federation Home Server field",
"LDAP_FederationHomeServer_Field_Description": "The Home Server can only be assigned on user creation. Changing this will have no effect on users that were already synced.",
"If_you_didnt_try_to_login_in_your_account_please_ignore_this_email": "If you didn't try to login in your account please ignore this email.",
"LDAP_Find_User_After_Login": "Find user after login",
"LDAP_Find_User_After_Login_Description": "Will perform a search of the user's DN after bind to ensure the bind was successful preventing login with empty passwords when allowed by the AD configuration.",
Expand Down
Loading