feat: apply group filter to LDAP sync process (#30088)

pull/29871/head^2
Rafael Tapia 2 years ago committed by GitHub
parent 9e5718002a
commit d45365436e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .changeset/warm-hornets-ring.md
  2. 23
      apps/meteor/app/importer/server/classes/ImportDataConverter.ts
  3. 6
      apps/meteor/app/importer/server/classes/VirtualDataConverter.ts
  4. 8
      apps/meteor/app/importer/server/definitions/IConversionCallbacks.ts
  5. 28
      apps/meteor/ee/server/lib/ldap/Manager.ts
  6. 44
      apps/meteor/server/lib/ldap/Connection.ts
  7. 2
      apps/meteor/server/lib/ldap/Manager.ts
  8. 2
      packages/core-typings/src/import/IImportRecord.ts

@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/core-typings": patch
---
Use group filter when set to LDAP sync process

@ -153,7 +153,7 @@ export class ImportDataConverter {
_id: new ObjectId().toHexString(),
data,
dataType: type,
...options,
options,
});
}
@ -454,13 +454,14 @@ export class ImportDataConverter {
const batchToInsert = new Set<IImportUser>();
for await (const { data, _id } of users) {
for await (const record of users) {
const { data, _id } = record;
if (this.aborted) {
break;
}
try {
if (beforeImportFn && !(await beforeImportFn(data, 'user'))) {
if (beforeImportFn && !(await beforeImportFn(record))) {
await this.skipRecord(_id);
skippedCount++;
continue;
@ -537,7 +538,7 @@ export class ImportDataConverter {
}
if (afterImportFn) {
await afterImportFn(data, 'user', isNewUser);
await afterImportFn(record, isNewUser);
}
} catch (e) {
this._logger.error(e);
@ -741,13 +742,14 @@ export class ImportDataConverter {
const rids: Array<string> = [];
const messages = await this.getMessagesToImport();
for await (const { data, _id } of messages) {
for await (const record of messages) {
const { data, _id } = record;
if (this.aborted) {
return;
}
try {
if (beforeImportFn && !(await beforeImportFn(data, 'message'))) {
if (beforeImportFn && !(await beforeImportFn(record))) {
await this.skipRecord(_id);
continue;
}
@ -816,7 +818,7 @@ export class ImportDataConverter {
}
if (afterImportFn) {
await afterImportFn(data, 'message', true);
await afterImportFn(record, true);
}
} catch (e) {
await this.saveError(_id, e instanceof Error ? e : new Error(String(e)));
@ -1116,13 +1118,14 @@ export class ImportDataConverter {
async convertChannels(startedByUserId: string, { beforeImportFn, afterImportFn, onErrorFn }: IConversionCallbacks = {}): Promise<void> {
const channels = await this.getChannelsToImport();
for await (const { data, _id } of channels) {
for await (const record of channels) {
const { data, _id } = record;
if (this.aborted) {
return;
}
try {
if (beforeImportFn && !(await beforeImportFn(data, 'channel'))) {
if (beforeImportFn && !(await beforeImportFn(record))) {
await this.skipRecord(_id);
continue;
}
@ -1151,7 +1154,7 @@ export class ImportDataConverter {
}
if (afterImportFn) {
await afterImportFn(data, 'channel', !existingRoom);
await afterImportFn(record, !existingRoom);
}
} catch (e) {
await this.saveError(_id, e instanceof Error ? e : new Error(String(e)));

@ -56,8 +56,8 @@ export class VirtualDataConverter extends ImportDataConverter {
return undefined;
}
public addUserSync(data: IImportUser): void {
return this.addObjectSync('user', data);
public addUserSync(data: IImportUser, options?: Record<string, any>): void {
return this.addObjectSync('user', data, options);
}
protected async addObject(type: IImportRecordType, data: IImportData, options: Record<string, any> = {}): Promise<void> {
@ -79,7 +79,7 @@ export class VirtualDataConverter extends ImportDataConverter {
_id: Random.id(),
data,
dataType: type,
...options,
options,
});
}

@ -1,11 +1,11 @@
import type { IImportUser, IImportMessage, IImportChannel, IImportRecordType } from '@rocket.chat/core-typings';
import type { IImportRecord } from '@rocket.chat/core-typings';
type ImporterBeforeImportCallback = {
(data: IImportUser | IImportChannel | IImportMessage, type: IImportRecordType): Promise<boolean>;
export type ImporterBeforeImportCallback = {
(data: IImportRecord): Promise<boolean>;
};
export type ImporterAfterImportCallback = {
(data: IImportUser | IImportChannel | IImportMessage, type: IImportRecordType, isNewRecord: boolean): Promise<void>;
(data: IImportRecord, isNewRecord: boolean): Promise<void>;
};
export interface IConversionCallbacks {

@ -1,9 +1,12 @@
import { Team } from '@rocket.chat/core-services';
import type { ILDAPEntry, IUser, IRoom, IRole, IImportUser } from '@rocket.chat/core-typings';
import type { ILDAPEntry, IUser, IRoom, IRole, IImportUser, IImportRecord } from '@rocket.chat/core-typings';
import { Users as UsersRaw, Roles, Subscriptions as SubscriptionsRaw, Rooms } from '@rocket.chat/models';
import type ldapjs from 'ldapjs';
import type { ImporterAfterImportCallback } from '../../../../app/importer/server/definitions/IConversionCallbacks';
import type {
ImporterAfterImportCallback,
ImporterBeforeImportCallback,
} from '../../../../app/importer/server/definitions/IConversionCallbacks';
import { addUserToRoom } from '../../../../app/lib/server/functions/addUserToRoom';
import { createRoom } from '../../../../app/lib/server/functions/createRoom';
import { removeUserFromRoom } from '../../../../app/lib/server/functions/removeUserFromRoom';
@ -43,9 +46,22 @@ export class LDAPEEManager extends LDAPManager {
await this.updateExistingUsers(ldap, converter);
}
const membersOfGroupFilter = await ldap.searchMembersOfGroupFilter();
await converter.convertUsers({
afterImportFn: (async (data: IImportUser, _type: string, isNewRecord: boolean): Promise<void> =>
this.advancedSync(ldap, data, converter, isNewRecord)) as ImporterAfterImportCallback,
beforeImportFn: (async ({ options }: IImportRecord): Promise<boolean> => {
if (!ldap.options.groupFilterEnabled || !ldap.options.groupFilterGroupMemberFormat) {
return true;
}
const memberFormat = ldap.options.groupFilterGroupMemberFormat
?.replace(/#{username}/g, options?.username || '#{username}')
.replace(/#{userdn}/g, options?.dn || '#{userdn}');
return membersOfGroupFilter.includes(memberFormat);
}) as ImporterBeforeImportCallback,
afterImportFn: (async ({ data }, isNewRecord: boolean): Promise<void> =>
this.advancedSync(ldap, data as IImportUser, converter, isNewRecord)) as ImporterAfterImportCallback,
});
} catch (error) {
logger.error(error);
@ -540,7 +556,7 @@ export class LDAPEEManager extends LDAPManager {
count++;
const userData = this.mapUserData(data);
converter.addUserSync(userData);
converter.addUserSync(userData, { dn: data.dn, username: this.getLdapUsername(data) });
return userData;
},
endCallback: (error: any): void => {
@ -564,7 +580,7 @@ export class LDAPEEManager extends LDAPManager {
if (ldapUser) {
const userData = this.mapUserData(ldapUser, user.username);
converter.addUserSync(userData);
converter.addUserSync(userData, { dn: ldapUser.dn, username: this.getLdapUsername(ldapUser) });
}
}
}

@ -9,6 +9,7 @@ import type {
import ldapjs from 'ldapjs';
import { settings } from '../../../app/settings/server';
import { ensureArray } from '../../../lib/utils/arrayUtils';
import { logger, connLogger, searchLogger, authLogger, bindLogger, mapLogger } from './Logger';
import { getLDAPConditionalSetting } from './getLDAPConditionalSetting';
@ -391,6 +392,49 @@ export class LDAPConnection {
return `(&${filter.join('')})`;
}
public async searchMembersOfGroupFilter(): Promise<string[]> {
if (!this.options.groupFilterEnabled) {
return [];
}
if (!this.options.groupFilterGroupMemberAttribute) {
return [];
}
if (!this.options.groupFilterGroupMemberFormat) {
searchLogger.debug(`LDAP Group Filter is enabled but no group member format is set.`);
return [];
}
const filter = ['(&'];
if (this.options.groupFilterObjectClass) {
filter.push(`(objectclass=${this.options.groupFilterObjectClass})`);
}
if (this.options.groupFilterGroupIdAttribute) {
filter.push(`(${this.options.groupFilterGroupIdAttribute}=${this.options.groupFilterGroupName})`);
}
filter.push(')');
const searchOptions: ldapjs.SearchOptions = {
filter: filter.join(''),
scope: 'sub',
};
searchLogger.debug({ msg: 'Group filter LDAP:', filter: searchOptions.filter });
const result = await this.searchRaw(this.options.baseDN, searchOptions);
if (!Array.isArray(result) || result.length === 0) {
searchLogger.debug({ msg: 'No groups found', result });
return [];
}
const members = this.extractLdapAttribute(result[0].raw[this.options.groupFilterGroupMemberAttribute]) as string | string[];
return ensureArray<string>(members);
}
public async isUserAcceptedByGroupFilter(username: string, userdn: string): Promise<boolean> {
if (!this.options.groupFilterEnabled) {
return true;

@ -483,7 +483,7 @@ export class LDAPManager {
return this.slugify(requestUsername);
}
private static getLdapUsername(ldapUser: ILDAPEntry): string | undefined {
protected static getLdapUsername(ldapUser: ILDAPEntry): string | undefined {
const usernameField = getLDAPConditionalSetting('LDAP_Username_Field') as string;
return this.getLdapDynamicValue(ldapUser, usernameField);
}

@ -9,7 +9,7 @@ export interface IImportRecord {
data: IImportData;
dataType: IImportRecordType;
_id: string;
options?: Record<string, unknown>;
options?: Record<string, any>;
errors?: Array<{
message: string;
stack?: string;

Loading…
Cancel
Save