Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMPROVE] Refactor + unit tests for federation-v2 #25680

Merged
merged 19 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b8019fd
refactor: refactor matrix bridge to support testing + unit tests
MarcosSpessatto May 28, 2022
b60edb6
Merge branch 'develop' into improvements/federation-refactor-and-tests
MarcosSpessatto May 28, 2022
9c1d24c
Merge branch 'develop' into improvements/federation-refactor-and-tests
MarcosSpessatto May 31, 2022
e2fc790
fix: fix conflict
MarcosSpessatto May 31, 2022
468e990
fix: types on test
MarcosSpessatto Jun 1, 2022
bfb02d1
fix: fix tests
MarcosSpessatto Jun 1, 2022
1329bd3
Merge branch 'develop' into improvements/federation-refactor-and-tests
MarcosSpessatto Jun 1, 2022
fc972e3
Merge branch 'develop' into improvements/federation-refactor-and-tests
MarcosSpessatto Jun 1, 2022
2320a96
chore: rename bridged to federated
MarcosSpessatto Jun 2, 2022
d7500f7
Merge branch 'develop' into improvements/federation-refactor-and-tests
MarcosSpessatto Jun 2, 2022
637bbf7
chore: remove duplicated config
MarcosSpessatto Jun 2, 2022
eba2d44
fix: fix yarn.lock
MarcosSpessatto Jun 2, 2022
9c22a75
Merge branch 'develop' into improvements/federation-refactor-and-tests
MarcosSpessatto Jun 2, 2022
3388435
chore: add migration for the renamed field
MarcosSpessatto Jun 2, 2022
26feb64
Merge branch 'develop' into improvements/federation-refactor-and-tests
MarcosSpessatto Jun 3, 2022
f97df41
Merge branch 'develop' into improvements/federation-refactor-and-tests
MarcosSpessatto Jun 3, 2022
b4cd537
chore: fix lint
MarcosSpessatto Jun 3, 2022
ee2b16d
Merge branch 'develop' into improvements/federation-refactor-and-tests
MarcosSpessatto Jun 6, 2022
145262e
fix: type
MarcosSpessatto Jun 6, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import { RoomType } from '@rocket.chat/apps-engine/definition/rooms';

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

export class FederationRoomServiceReceiver {
constructor(
private rocketRoomAdapter: RocketChatRoomAdapter,
private rocketUserAdapter: RocketChatUserAdapter,
private rocketMessageAdapter: RocketChatMessageAdapter,
private rocketSettingsAdapter: RocketChatSettingsAdapter,
private bridge: IFederationBridge,
) {} // eslint-disable-line no-empty-function

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

if ((await this.rocketRoomAdapter.getFederatedRoomByExternalId(externalRoomId)) || wasInternallyProgramaticallyCreated) {
return;
}

if (!(await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviterId))) {
const externalUserProfileInformation = await this.bridge.getUserProfileInformation(externalInviterId);
const name = externalUserProfileInformation?.displayname || normalizedInviterId;
const federatedCreatorUser = FederatedUser.createInstance(externalInviterId, {
name,
username: normalizedInviterId,
existsOnlyOnProxyServer: false,
});

await this.rocketUserAdapter.createFederatedUser(federatedCreatorUser);
}
const creator = await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviterId);
const newFederatedRoom = FederatedRoom.createInstance(
externalRoomId,
normalizedRoomId,
creator as FederatedUser,
roomType || RoomType.CHANNEL,
externalRoomName,
);
await this.rocketRoomAdapter.createFederatedRoom(newFederatedRoom);
}

public async changeRoomMembership(roomChangeMembershipInput: FederationRoomChangeMembershipDto): Promise<void> {
const {
externalRoomId,
normalizedInviteeId,
normalizedRoomId,
normalizedInviterId,
externalRoomName,
externalInviteeId,
externalInviterId,
inviteeUsernameOnly,
inviterUsernameOnly,
eventOrigin,
roomType,
leave,
} = roomChangeMembershipInput;
const affectedFederatedRoom = await this.rocketRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!affectedFederatedRoom && eventOrigin === EVENT_ORIGIN.LOCAL) {
throw new Error(`Could not find room with external room id: ${externalRoomId}`);
}
const isInviterFromTheSameHomeServer = await this.bridge.isUserIdFromTheSameHomeserver(
externalInviterId,
this.rocketSettingsAdapter.getHomeServerDomain(),
);
const isInviteeFromTheSameHomeServer = await this.bridge.isUserIdFromTheSameHomeserver(
externalInviteeId,
this.rocketSettingsAdapter.getHomeServerDomain(),
);

if (!(await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviterId))) {
const externalUserProfileInformation = await this.bridge.getUserProfileInformation(externalInviterId);
const name = externalUserProfileInformation.displayname || normalizedInviterId;
const username = isInviterFromTheSameHomeServer ? inviterUsernameOnly : normalizedInviterId;
const federatedInviterUser = FederatedUser.createInstance(externalInviterId, {
name,
username,
existsOnlyOnProxyServer: isInviterFromTheSameHomeServer,
});

await this.rocketUserAdapter.createFederatedUser(federatedInviterUser);
}

if (!(await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviteeId))) {
const externalUserProfileInformation = await this.bridge.getUserProfileInformation(externalInviteeId);
const name = externalUserProfileInformation.displayname || normalizedInviteeId;
const username = isInviteeFromTheSameHomeServer ? inviteeUsernameOnly : normalizedInviteeId;
const federatedInviteeUser = FederatedUser.createInstance(externalInviteeId, {
name,
username,
existsOnlyOnProxyServer: isInviteeFromTheSameHomeServer,
});

await this.rocketUserAdapter.createFederatedUser(federatedInviteeUser);
}

const federatedInviteeUser = await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviteeId);
const federatedInviterUser = await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviterId);

if (!affectedFederatedRoom && eventOrigin === EVENT_ORIGIN.REMOTE) {
const members = [federatedInviterUser, federatedInviteeUser] as any[];
const newFederatedRoom = FederatedRoom.createInstance(
externalRoomId,
normalizedRoomId,
federatedInviterUser as FederatedUser,
roomType,
externalRoomName,
members,
);

await this.rocketRoomAdapter.createFederatedRoom(newFederatedRoom);
await this.bridge.joinRoom(externalRoomId, externalInviteeId);
}
const federatedRoom = affectedFederatedRoom || (await this.rocketRoomAdapter.getFederatedRoomByExternalId(externalRoomId));

if (leave) {
return this.rocketRoomAdapter.removeUserFromRoom(
federatedRoom as FederatedRoom,
federatedInviteeUser as FederatedUser,
federatedInviterUser as FederatedUser,
);
}
await this.rocketRoomAdapter.addUserToRoom(
federatedRoom as FederatedRoom,
federatedInviteeUser as FederatedUser,
federatedInviterUser as FederatedUser,
);
}

public async receiveExternalMessage(roomSendInternalMessageInput: FederationRoomSendInternalMessageDto): Promise<void> {
const { externalRoomId, externalSenderId, text } = roomSendInternalMessageInput;

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

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

await this.rocketMessageAdapter.sendMessage(senderUser, text, federatedRoom);
}

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

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

if (federatedRoom.isDirectMessage()) {
return;
}

federatedRoom.setRoomType(roomType);
await this.rocketRoomAdapter.updateRoomType(federatedRoom);
}

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

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

if (federatedRoom.isDirectMessage()) {
return;
}

federatedRoom.changeRoomName(normalizedRoomName);

await this.rocketRoomAdapter.updateRoomName(federatedRoom);
}

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

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

if (federatedRoom.isDirectMessage()) {
return;
}

federatedRoom.changeRoomTopic(roomTopic);

await this.rocketRoomAdapter.updateRoomTopic(federatedRoom);
}
}
128 changes: 128 additions & 0 deletions apps/meteor/app/federation-v2/server/application/RoomServiceSender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { RoomType } from '@rocket.chat/apps-engine/definition/rooms';
import { IMessage, IRoom, IUser } from '@rocket.chat/core-typings';

import { FederatedRoom } from '../domain/FederatedRoom';
import { FederatedUser } from '../domain/FederatedUser';
import { IFederationBridge } from '../domain/IFederationBridge';
import { RocketChatNotificationAdapter } from '../infrastructure/rocket-chat/adapters/Notification';
import { RocketChatRoomAdapter } from '../infrastructure/rocket-chat/adapters/Room';
import { RocketChatSettingsAdapter } from '../infrastructure/rocket-chat/adapters/Settings';
import { RocketChatUserAdapter } from '../infrastructure/rocket-chat/adapters/User';
import { FederationRoomInviteUserDto, FederationRoomSendExternalMessageDto } from './input/RoomSenderDto';

export class FederationRoomServiceSender {
constructor(
private rocketRoomAdapter: RocketChatRoomAdapter,
private rocketUserAdapter: RocketChatUserAdapter,
private rocketSettingsAdapter: RocketChatSettingsAdapter,
private rocketNotificationAdapter: RocketChatNotificationAdapter,
private bridge: IFederationBridge,
) {} // eslint-disable-line no-empty-function

public async inviteUserToAFederatedRoom(roomInviteUserInput: FederationRoomInviteUserDto): Promise<void> {
const { normalizedInviteeId, rawInviteeId, internalInviterId, inviteeUsernameOnly, internalRoomId } = roomInviteUserInput;

if (!(await this.rocketUserAdapter.getFederatedUserByInternalId(internalInviterId))) {
const internalUser = (await this.rocketUserAdapter.getInternalUserById(internalInviterId)) as IUser;
const externalInviterId = await this.bridge.createUser(
internalUser.username as string,
internalUser.name as string,
this.rocketSettingsAdapter.getHomeServerDomain(),
);
const federatedInviterUser = FederatedUser.createInstance(externalInviterId, {
name: internalUser.name as string,
username: internalUser.username as string,
existsOnlyOnProxyServer: true,
});
await this.rocketUserAdapter.createFederatedUser(federatedInviterUser);
}

if (!(await this.rocketUserAdapter.getFederatedUserByInternalUsername(normalizedInviteeId))) {
const externalUserProfileInformation = await this.bridge.getUserProfileInformation(rawInviteeId);
const name = externalUserProfileInformation?.displayname || normalizedInviteeId;
const federatedInviteeUser = FederatedUser.createInstance(rawInviteeId, {
name,
username: normalizedInviteeId,
existsOnlyOnProxyServer: false,
});

await this.rocketUserAdapter.createFederatedUser(federatedInviteeUser);
}

const federatedInviterUser = (await this.rocketUserAdapter.getFederatedUserByInternalId(internalInviterId)) as FederatedUser;
const federatedInviteeUser = (await this.rocketUserAdapter.getFederatedUserByInternalUsername(normalizedInviteeId)) as FederatedUser;
const isInviteeFromTheSameHomeServer = await this.bridge.isUserIdFromTheSameHomeserver(
rawInviteeId,
this.rocketSettingsAdapter.getHomeServerDomain(),
);

if (!(await this.rocketRoomAdapter.getFederatedRoomByInternalId(internalRoomId))) {
const internalRoom = (await this.rocketRoomAdapter.getInternalRoomById(internalRoomId)) as IRoom;
const roomName = (internalRoom.fname || internalRoom.name) as string;
const externalRoomId = await this.bridge.createRoom(
federatedInviterUser.externalId,
federatedInviteeUser.externalId,
internalRoom.t as RoomType,
roomName,
internalRoom.topic,
);
const newFederatedRoom = FederatedRoom.createInstance(
externalRoomId,
externalRoomId,
federatedInviterUser,
internalRoom.t as RoomType,
roomName,
);
await this.rocketRoomAdapter.updateFederatedRoomByInternalRoomId(internalRoom._id, newFederatedRoom);
}

const federatedRoom = (await this.rocketRoomAdapter.getFederatedRoomByInternalId(internalRoomId)) as FederatedRoom;
const wasInvitedWhenTheRoomWasCreated = federatedRoom.isDirectMessage();
if (isInviteeFromTheSameHomeServer) {
await this.bridge.createUser(
inviteeUsernameOnly,
federatedInviteeUser.internalReference.name as string,
this.rocketSettingsAdapter.getHomeServerDomain(),
);
await this.bridge.inviteToRoom(federatedRoom.externalId, federatedInviterUser.externalId, federatedInviteeUser.externalId);
await this.bridge.joinRoom(federatedRoom.externalId, federatedInviteeUser.externalId);
} else if (!wasInvitedWhenTheRoomWasCreated) {
this.bridge.inviteToRoom(federatedRoom.externalId, federatedInviterUser.externalId, federatedInviteeUser.externalId).catch(() => {
this.rocketNotificationAdapter.notifyWithEphemeralMessage(
'Federation_Matrix_only_owners_can_invite_users',
federatedInviterUser?.internalReference?._id,
internalRoomId,
federatedInviterUser?.internalReference?.language,
);
});
}
await this.rocketRoomAdapter.addUserToRoom(federatedRoom, federatedInviteeUser, federatedInviterUser);
}

public async sendMessageFromRocketChat(roomSendExternalMessageInput: FederationRoomSendExternalMessageDto): Promise<IMessage> {
const { internalRoomId, internalSenderId, message } = roomSendExternalMessageInput;

const federatedSender = await this.rocketUserAdapter.getFederatedUserByInternalId(internalSenderId);
const federatedRoom = await this.rocketRoomAdapter.getFederatedRoomByInternalId(internalRoomId);

if (!federatedSender) {
throw new Error(`Could not find user id for ${internalSenderId}`);
}
if (!federatedRoom) {
throw new Error(`Could not find room id for ${internalRoomId}`);
}

await this.bridge.sendMessage(federatedRoom.externalId, federatedSender.externalId, message.msg);

return message;
}

public async isAFederatedRoom(internalRoomId: string): Promise<boolean> {
if (!internalRoomId) {
return false;
}
const federatedRoom = await this.rocketRoomAdapter.getFederatedRoomByInternalId(internalRoomId);

return Boolean(federatedRoom?.isFederated());
}
}
Loading