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
17 changes: 17 additions & 0 deletions apps/meteor/ee/server/configuration/ldap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,23 @@ Meteor.startup(async () => {
() => LDAPEE.syncLogout(),
);

const addAbacCronJob = configureBackgroundSync(
'LDAP_AbacSync',
'LDAP_Background_Sync_ABAC_Attributes',
'LDAP_Background_Sync_ABAC_Attributes_Interval',
() => LDAPEE.syncAbacAttributes(),
);

settings.watchMultiple(['LDAP_Background_Sync', 'LDAP_Background_Sync_Interval'], addCronJob);
settings.watchMultiple(['LDAP_Background_Sync_Avatars', 'LDAP_Background_Sync_Avatars_Interval'], addAvatarCronJob);
settings.watchMultiple(['LDAP_Sync_AutoLogout_Enabled', 'LDAP_Sync_AutoLogout_Interval'], addLogoutCronJob);
settings.watchMultiple(['LDAP_Background_Sync_ABAC_Attributes', 'LDAP_Background_Sync_ABAC_Attributes_Interval'], addAbacCronJob);

settings.watch('LDAP_Enable', async () => {
await addCronJob();
await addAvatarCronJob();
await addLogoutCronJob();
await addAbacCronJob();
});

settings.watch<string>('LDAP_Groups_To_Rocket_Chat_Teams', (value) => {
Expand All @@ -78,6 +87,14 @@ Meteor.startup(async () => {
}
});

settings.watch<string>('LDAP_ABAC_AttributeMap', (value) => {
try {
LDAPEEManager.validateLDAPABACAttributeMap(value);
} catch (error) {
logger.error(error);
}
});

callbacks.add(
'mapLDAPUserData',
(userData: IImportUser, ldapUser?: ILDAPEntry) => {
Expand Down
70 changes: 69 additions & 1 deletion apps/meteor/ee/server/lib/ldap/Manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Team } from '@rocket.chat/core-services';
import { Abac, Team } from '@rocket.chat/core-services';
import type { ILDAPEntry, IUser, IRoom, IRole, IImportUser, IImportRecord } from '@rocket.chat/core-typings';
import { License } from '@rocket.chat/license';
import { Users, Roles, Subscriptions as SubscriptionsRaw, Rooms } from '@rocket.chat/models';
import type ldapjs from 'ldapjs';

Expand Down Expand Up @@ -102,6 +103,30 @@ export class LDAPEEManager extends LDAPManager {
}
}

public static async syncAbacAttributes(): Promise<void> {
if (
!settings.get('LDAP_Enable') ||
!settings.get('LDAP_Background_Sync_ABAC_Attributes') ||
!License.hasModule('abac') ||
!settings.get('ABAC_Enabled')
) {
return;
}

try {
const ldap = new LDAPConnection();
await ldap.connect();

try {
await this.updateUserAbacAttributes(ldap);
} finally {
ldap.disconnect();
}
} catch (error) {
logger.error(error);
}
}

public static validateLDAPTeamsMappingChanges(json: string): void {
if (!json) {
return;
Expand All @@ -123,6 +148,32 @@ export class LDAPEEManager extends LDAPManager {
}
}

public static validateLDAPABACAttributeMap(json: string): void {
if (!json) {
return;
}

const mappedAttributes = this.parseJson(json);

// attributes are { key: value } with key being the ldap attribute and value being the abac attribute in rocketchat
// both strings
// There's no need for the attribute to exist in rocketchat, we just add whatever the admin wants to map

if (!mappedAttributes || Object.keys(mappedAttributes).length === 0) {
return;
}

const validStructureMapping = Object.entries(mappedAttributes).every(
([key, value]) => typeof key === 'string' && typeof value === 'string',
);

if (!validStructureMapping) {
throw new Error(
'Please verify your mapping for LDAP X RocketChat ABAC Attributes. The structure is invalid, the structure should be an object like: {key: LdapAttribute, value: RocketChatAbacAttribute}',
);
}
}

public static async syncLogout(): Promise<void> {
if (settings.get('LDAP_Enable') !== true || settings.get('LDAP_Sync_AutoLogout_Enabled') !== true) {
return;
Expand Down Expand Up @@ -665,6 +716,23 @@ export class LDAPEEManager extends LDAPManager {
}
}

private static async updateUserAbacAttributes(ldap: LDAPConnection): Promise<void> {
const mapping = this.parseJson(settings.get('LDAP_ABAC_AttributeMap'));
if (!mapping) {
logger.error('LDAP to ABAC attribute mapping is not valid JSON');
return;
}

for await (const user of Users.findLDAPUsers()) {
const ldapUser = await this.findLDAPUser(ldap, user);
if (!ldapUser) {
continue;
}

await Abac.addSubjectAttributes(user, ldapUser, mapping);
}
}

private static async findLDAPUser(ldap: LDAPConnection, user: IUser): Promise<ILDAPEntry | undefined> {
if (user.services?.ldap?.id) {
return ldap.findOneById(user.services.ldap.id, user.services.ldap.idAttribute);
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/ee/server/local-services/ldap/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ export class LDAPEEService extends ServiceClassInternal implements ILDAPEEServic
async syncLogout(): Promise<void> {
return LDAPEEManager.syncLogout();
}

async syncAbacAttributes(): Promise<void> {
return LDAPEEManager.syncAbacAttributes();
}
}
1 change: 1 addition & 0 deletions apps/meteor/ee/server/sdk/types/ILDAPEEService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export interface ILDAPEEService {
sync(): Promise<void>;
syncAvatars(): Promise<void>;
syncLogout(): Promise<void>;
syncAbacAttributes(): Promise<void>;
}
24 changes: 24 additions & 0 deletions apps/meteor/ee/server/settings/ldap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,30 @@ export function addSettings(): Promise<void> {
invalidValue: '',
});
});

await this.section('LDAP_DataSync_ABAC', async function () {
await this.add('LDAP_Background_Sync_ABAC_Attributes', false, {
type: 'boolean',
enableQuery,
invalidValue: false,
modules: ['abac', 'ldap-enterprise'],
});

await this.add('LDAP_Background_Sync_ABAC_Attributes_Interval', '0 0 * * *', {
type: 'string',
enableQuery: [enableQuery, { _id: 'LDAP_Background_Sync_ABAC_Attributes', value: true }],
invalidValue: '0 0 * * *',
modules: ['abac', 'ldap-enterprise'],
});

await this.add('LDAP_ABAC_AttributeMap', '{}', {
type: 'code',
multiline: true,
enableQuery: [enableQuery, { _id: 'LDAP_Background_Sync_ABAC_Attributes', value: true }],
invalidValue: '{}',
modules: ['abac', 'ldap-enterprise'],
});
});
},
);
});
Expand Down
34 changes: 34 additions & 0 deletions ee/packages/abac/src/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { ILDAPEntry, IAbacAttributeDefinition } from '@rocket.chat/core-typings';

export const extractAttribute = (ldapUser: ILDAPEntry, ldapKey: string, abacKey: string): IAbacAttributeDefinition | undefined => {
if (!ldapKey || !abacKey) {
return;
}
const raw = ldapUser?.[ldapKey];
if (!raw) {
return;
}
const valuesSet = new Set<string>();
const addIfValid = (value: unknown) => {
if (typeof value !== 'string') {
return;
}
const trimmed = value.trim();
if (!trimmed.length) {
return;
}
valuesSet.add(trimmed);
};

if (Array.isArray(raw)) {
for (const v of raw) {
addIfValid(v);
}
} else {
addIfValid(raw);
}
if (!valuesSet.size) {
return;
}
return { key: abacKey, values: Array.from(valuesSet) };
};
Loading
Loading