Skip to content

Commit

Permalink
[IMPROVE] General federation improvements (#26150)
Browse files Browse the repository at this point in the history
Co-authored-by: Tasso Evangelista <[email protected]>
  • Loading branch information
2 people authored and csuarez committed Aug 26, 2022
1 parent 3fdf9b3 commit dc0c691
Show file tree
Hide file tree
Showing 156 changed files with 6,068 additions and 3,493 deletions.
1 change: 0 additions & 1 deletion apps/meteor/app/federation-v2/client/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RoomType } from '@rocket.chat/apps-engine/definition/rooms';
import type { IRoom, ValueOf } from '@rocket.chat/core-typings';
import { isDirectMessageRoom } from '@rocket.chat/core-typings';

import { RoomMemberActions } from '../../../../../definition/IRoomTypeConfig';
import { RoomMemberActions } from '../../../definition/IRoomTypeConfig';

const allowedActionsInFederatedRooms: ValueOf<typeof RoomMemberActions>[] = [
RoomMemberActions.REMOVE_USER,
Expand All @@ -11,14 +11,8 @@ const allowedActionsInFederatedRooms: ValueOf<typeof RoomMemberActions>[] = [
];

export class Federation {
public static isAFederatedRoom(room: IRoom): boolean {
return room.federated === true;
}

public static actionAllowed(room: IRoom, action: ValueOf<typeof RoomMemberActions>): boolean {
return room.t === RoomType.DIRECT_MESSAGE && action === RoomMemberActions.REMOVE_USER
? false
: allowedActionsInFederatedRooms.includes(action);
return isDirectMessageRoom(room) && action === RoomMemberActions.REMOVE_USER ? false : allowedActionsInFederatedRooms.includes(action);
}

public static isAFederatedUsername(username: string): boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { FederatedUser } from '../domain/FederatedUser';
import type { IFederationBridge } from '../domain/IFederationBridge';
import type { RocketChatSettingsAdapter } from '../infrastructure/rocket-chat/adapters/Settings';
import type { RocketChatUserAdapter } from '../infrastructure/rocket-chat/adapters/User';

export abstract class FederationService {
protected internalHomeServerDomain: string;

constructor(
protected bridge: IFederationBridge,
protected internalUserAdapter: RocketChatUserAdapter,
protected internalSettingsAdapter: RocketChatSettingsAdapter,
) {
this.internalHomeServerDomain = this.internalSettingsAdapter.getHomeServerDomain();
}

protected async createFederatedUser(
externalUserId: string,
username: string,
existsOnlyOnProxyServer = false,
providedName?: string,
): Promise<void> {
const externalUserProfileInformation = await this.bridge.getUserProfileInformation(externalUserId);
const name = externalUserProfileInformation?.displayName || providedName || username;
const federatedUser = FederatedUser.createInstance(externalUserId, {
name,
username,
existsOnlyOnProxyServer,
});

await this.internalUserAdapter.createFederatedUser(federatedUser);
}

protected async createFederatedUserForInviterUsingLocalInformation(internalInviterId: string): Promise<string> {
const internalUser = await this.internalUserAdapter.getInternalUserById(internalInviterId);
if (!internalUser || !internalUser?.username) {
throw new Error(`Could not find user id for ${internalInviterId}`);
}
const name = internalUser.name || internalUser.username;
const externalInviterId = await this.bridge.createUser(internalUser.username, name, this.internalHomeServerDomain);
const existsOnlyOnProxyServer = true;
await this.createFederatedUser(externalInviterId, internalUser.username, existsOnlyOnProxyServer, name);

return externalInviterId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
import { RoomType } from '@rocket.chat/apps-engine/definition/rooms';
import { isDirectMessageRoom } from '@rocket.chat/core-typings';

import { DirectMessageFederatedRoom, FederatedRoom } from '../domain/FederatedRoom';
import { FederatedUser } from '../domain/FederatedUser';
import { EVENT_ORIGIN } from '../domain/IFederationBridge';
import type { IFederationBridge } from '../domain/IFederationBridge';
import type { RocketChatMessageAdapter } from '../infrastructure/rocket-chat/adapters/Message';
import type { RocketChatRoomAdapter } from '../infrastructure/rocket-chat/adapters/Room';
import type { RocketChatSettingsAdapter } from '../infrastructure/rocket-chat/adapters/Settings';
import type { RocketChatUserAdapter } from '../infrastructure/rocket-chat/adapters/User';
import type {
FederationRoomCreateInputDto,
FederationRoomChangeMembershipDto,
FederationRoomReceiveExternalMessageDto,
FederationRoomChangeJoinRulesDto,
FederationRoomChangeNameDto,
FederationRoomChangeTopicDto,
} from './input/RoomReceiverDto';
import { FederationService } from './AbstractFederationService';

export class FederationRoomServiceListener extends FederationService {
constructor(
protected internalRoomAdapter: RocketChatRoomAdapter,
protected internalUserAdapter: RocketChatUserAdapter,
protected internalMessageAdapter: RocketChatMessageAdapter,
protected internalSettingsAdapter: RocketChatSettingsAdapter,
protected bridge: IFederationBridge,
) {
super(bridge, internalUserAdapter, internalSettingsAdapter);
}

public async onCreateRoom(roomCreateInput: FederationRoomCreateInputDto): Promise<void> {
const {
externalRoomId,
externalInviterId,
normalizedInviterId,
externalRoomName,
normalizedRoomId,
roomType,
wasInternallyProgramaticallyCreated = false,
internalRoomId = '',
} = roomCreateInput;

if (await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId)) {
return;
}
if (wasInternallyProgramaticallyCreated) {
const room = await this.internalRoomAdapter.getInternalRoomById(internalRoomId);
if (!room || !isDirectMessageRoom(room)) {
return;
}
await this.internalRoomAdapter.updateFederatedRoomByInternalRoomId(internalRoomId, externalRoomId);
return;
}

const creatorUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalInviterId);
if (!creatorUser) {
await this.createFederatedUser(externalInviterId, normalizedInviterId);
}
const creator = creatorUser || (await this.internalUserAdapter.getFederatedUserByExternalId(externalInviterId));
if (!creator) {
throw new Error('Creator user not found');
}
const newFederatedRoom = FederatedRoom.createInstance(
externalRoomId,
normalizedRoomId,
creator,
roomType || RoomType.CHANNEL,
externalRoomName,
);
await this.internalRoomAdapter.createFederatedRoom(newFederatedRoom);
}

public async onChangeRoomMembership(roomChangeMembershipInput: FederationRoomChangeMembershipDto): Promise<void> {
const {
externalRoomId,
normalizedInviteeId,
normalizedRoomId,
normalizedInviterId,
externalRoomName,
externalInviteeId,
externalInviterId,
inviteeUsernameOnly,
inviterUsernameOnly,
eventOrigin,
roomType,
leave,
} = roomChangeMembershipInput;
const wasGeneratedOnTheProxyServer = eventOrigin === EVENT_ORIGIN.LOCAL;
const affectedFederatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);

if (wasGeneratedOnTheProxyServer && !affectedFederatedRoom) {
throw new Error(`Could not find room with external room id: ${externalRoomId}`);
}

const isInviterFromTheSameHomeServer = FederatedUser.isOriginalFromTheProxyServer(
this.bridge.extractHomeserverOrigin(externalInviterId),
this.internalHomeServerDomain,
);
const isInviteeFromTheSameHomeServer = FederatedUser.isOriginalFromTheProxyServer(
this.bridge.extractHomeserverOrigin(externalInviteeId),
this.internalHomeServerDomain,
);
const inviterUsername = isInviterFromTheSameHomeServer ? inviterUsernameOnly : normalizedInviterId;
const inviteeUsername = isInviteeFromTheSameHomeServer ? inviteeUsernameOnly : normalizedInviteeId;

const inviterUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalInviterId);
if (!inviterUser) {
await this.createFederatedUser(externalInviterId, inviterUsername, isInviterFromTheSameHomeServer);
}

const inviteeUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalInviteeId);
if (!inviteeUser) {
await this.createFederatedUser(externalInviteeId, inviteeUsername, isInviteeFromTheSameHomeServer);
}
const federatedInviteeUser = inviteeUser || (await this.internalUserAdapter.getFederatedUserByExternalId(externalInviteeId));
const federatedInviterUser = inviterUser || (await this.internalUserAdapter.getFederatedUserByExternalId(externalInviterId));

if (!federatedInviteeUser || !federatedInviterUser) {
throw new Error('Invitee or inviter user not found');
}

if (!wasGeneratedOnTheProxyServer && !affectedFederatedRoom) {
if (!roomType) {
return;
}
if (isDirectMessageRoom({ t: roomType })) {
const members = [federatedInviterUser, federatedInviteeUser];
const newFederatedRoom = DirectMessageFederatedRoom.createInstance(externalRoomId, federatedInviterUser, members);
await this.internalRoomAdapter.createFederatedRoomForDirectMessage(newFederatedRoom);
await this.bridge.joinRoom(externalRoomId, externalInviteeId);
return;
}
const newFederatedRoom = FederatedRoom.createInstance(
externalRoomId,
normalizedRoomId,
federatedInviterUser,
roomType,
externalRoomName,
);

await this.internalRoomAdapter.createFederatedRoom(newFederatedRoom);
await this.bridge.joinRoom(externalRoomId, externalInviteeId);
}

const federatedRoom = affectedFederatedRoom || (await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId));
if (!federatedRoom) {
throw new Error(`Could not find room with external room id: ${externalRoomId}`);
}

if (leave) {
const inviteeAlreadyJoinedTheInternalRoom = await this.internalRoomAdapter.isUserAlreadyJoined(
federatedRoom.getInternalId(),
federatedInviteeUser.getInternalId(),
);
if (!inviteeAlreadyJoinedTheInternalRoom) {
return;
}
await this.internalRoomAdapter.removeUserFromRoom(federatedRoom, federatedInviteeUser, federatedInviterUser);
return;
}
if (!wasGeneratedOnTheProxyServer && federatedRoom.isDirectMessage()) {
const directMessageRoom = federatedRoom as DirectMessageFederatedRoom;
if (directMessageRoom.isUserPartOfTheRoom(federatedInviteeUser)) {
return;
}
directMessageRoom.addMember(federatedInviteeUser);
const newFederatedRoom = DirectMessageFederatedRoom.createInstance(
externalRoomId,
federatedInviterUser,
directMessageRoom.getMembers(),
);
await this.internalRoomAdapter.removeDirectMessageRoom(federatedRoom);
await this.internalRoomAdapter.createFederatedRoomForDirectMessage(newFederatedRoom);
return;
}

await this.internalRoomAdapter.addUserToRoom(federatedRoom, federatedInviteeUser, federatedInviterUser);
}

public async onExternalMessageReceived(roomReceiveExternalMessageInput: FederationRoomReceiveExternalMessageDto): Promise<void> {
const { externalRoomId, externalSenderId, messageText } = roomReceiveExternalMessageInput;

const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!federatedRoom) {
return;
}

const senderUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalSenderId);
if (!senderUser) {
return;
}

await this.internalMessageAdapter.sendMessage(senderUser, federatedRoom, messageText);
}

public async onChangeJoinRules(roomJoinRulesChangeInput: FederationRoomChangeJoinRulesDto): Promise<void> {
const { externalRoomId, roomType } = roomJoinRulesChangeInput;

const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!federatedRoom) {
return;
}

const notAllowedChangeJoinRules = federatedRoom.isDirectMessage();
if (notAllowedChangeJoinRules) {
return;
}

federatedRoom.changeRoomType(roomType);
await this.internalRoomAdapter.updateRoomType(federatedRoom);
}

public async onChangeRoomName(roomChangeNameInput: FederationRoomChangeNameDto): Promise<void> {
const { externalRoomId, normalizedRoomName, externalSenderId } = roomChangeNameInput;

const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!federatedRoom) {
return;
}

if (!federatedRoom.shouldUpdateRoomName(normalizedRoomName)) {
return;
}

const federatedUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalSenderId);
if (!federatedUser) {
return;
}

federatedRoom.changeRoomName(normalizedRoomName);

await this.internalRoomAdapter.updateRoomName(federatedRoom, federatedUser);
}

public async onChangeRoomTopic(roomChangeTopicInput: FederationRoomChangeTopicDto): Promise<void> {
const { externalRoomId, roomTopic, externalSenderId } = roomChangeTopicInput;

const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!federatedRoom) {
return;
}

if (!federatedRoom.shouldUpdateRoomTopic(roomTopic)) {
return;
}

const federatedUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalSenderId);
if (!federatedUser) {
return;
}

federatedRoom.changeRoomTopic(roomTopic);

await this.internalRoomAdapter.updateRoomTopic(federatedRoom, federatedUser);
}
}
Loading

0 comments on commit dc0c691

Please sign in to comment.