From 6fd912bbe93e2e9fb085882293c1d190022daa1d Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 4 Aug 2023 18:25:40 +0100 Subject: [PATCH 1/3] Add privacy filter tests --- spec/e2e/privacyfilter.spec.ts | 173 +++++++++++++++++++++++++++++++++ spec/util/e2e-test.ts | 30 +++++- 2 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 spec/e2e/privacyfilter.spec.ts diff --git a/spec/e2e/privacyfilter.spec.ts b/spec/e2e/privacyfilter.spec.ts new file mode 100644 index 000000000..7f50637c4 --- /dev/null +++ b/spec/e2e/privacyfilter.spec.ts @@ -0,0 +1,173 @@ +import { TestIrcServer } from "matrix-org-irc"; +import { IrcBridgeE2ETest } from "../util/e2e-test"; +import { describe, it, expect } from "@jest/globals"; + + +describe('Connection pooling', () => { + let testEnv: IrcBridgeE2ETest; + + beforeEach(async () => { + const alice = TestIrcServer.generateUniqueNick('alice'); + const bannedUser = TestIrcServer.generateUniqueNick('banneduser'); + // Initial run of the bridge to setup a testing environment + testEnv = await IrcBridgeE2ETest.createTestEnv({ + matrixLocalparts: [alice, bannedUser], + ircNicks: ['bob'], + ircServerConfig: { + membershipLists: { + enabled: true, + floodDelayMs: 100, + global: { + ircToMatrix: { + incremental: true, + initial: true, + requireMatrixJoined: true, + }, + matrixToIrc: { + incremental: true, + initial: true, + } + } + }, + excludedUsers: [{ + regex: `@${bannedUser}.*`, + kickReason: 'Test kick', + }] + } + }); + await testEnv.setUp(); + }); + + // Ensure we always tear down + afterEach(() => { + return testEnv.tearDown(); + }); + + it('should be able to send a message with the privacy filter on', async () => { + const channel = `#${TestIrcServer.generateUniqueNick("test")}`; + + const { homeserver } = testEnv; + const alice = homeserver.users[0].client; + const { bob } = testEnv.ircTest.clients; + + // Create the channel + await bob.join(channel); + + const adminRoomId = await testEnv.createAdminRoomHelper(alice); + const cRoomId = await testEnv.joinChannelHelper(alice, adminRoomId, channel); + + // And finally wait for bob to appear. + const bobUserId = `@irc_${bob.nick}:${homeserver.domain}`; + await alice.waitForRoomEvent( + {eventType: 'm.room.member', sender: bobUserId, stateKey: bobUserId, roomId: cRoomId} + ); + + + // Send some messages + const aliceMsg = bob.waitForEvent('message', 10000); + const bobMsg = alice.waitForRoomEvent( + {eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId} + ); + alice.sendText(cRoomId, "Hello bob!"); + await aliceMsg; + bob.say(channel, "Hi alice!"); + await bobMsg; + }); + + it('should not bridge messages with an excluded user', async () => { + const channel = `#${TestIrcServer.generateUniqueNick("test")}`; + + const { homeserver } = testEnv; + const alice = homeserver.users[0].client; + const bannedUser = homeserver.users[1].client; + const { bob } = testEnv.ircTest.clients; + + // Create the channel + await bob.join(channel); + + const adminRoomId = await testEnv.createAdminRoomHelper(alice); + const cRoomId = await testEnv.createProvisionedRoom(alice, adminRoomId, channel, false); + + // And finally wait for bob to appear. + const bobUserId = `@irc_${bob.nick}:${homeserver.domain}`; + await alice.waitForRoomEvent( + {eventType: 'm.room.member', sender: bobUserId, stateKey: bobUserId, roomId: cRoomId} + ); + const aliceMsg = bob.waitForEvent('message', 10000); + alice.sendText(cRoomId, "Hello bob!"); + await aliceMsg; + + // Note, the bridge can't kick bannedUser due to lacking perms. + await bannedUser.joinRoom(cRoomId); + const message = alice.waitForRoomEvent( + {eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId} + ); + const connectionStateEv = alice.waitForRoomEvent({ + eventType: 'org.matrix.appservice-irc.connection', + sender: testEnv.ircBridge.appServiceUserId, + roomId: cRoomId + }); + await bob.say(channel, "Hi alice!"); + try { + await message; + throw Error('Expected message to not be viewable.'); + } + catch (ex) { + if (!ex.message.startsWith(`Timed out waiting for m.room.message from ${bobUserId} in ${cRoomId}`)) { + throw ex; + } + } + const connectionEventData = (await connectionStateEv).data; + expect(connectionEventData.content.blocked).toBe(true); + }); + + fit('should unblock a blocked channel if all excluded users leave', async () => { + const channel = `#${TestIrcServer.generateUniqueNick("test")}`; + + const { homeserver } = testEnv; + const alice = homeserver.users[0].client; + const bannedUser = homeserver.users[1].client; + const { bob } = testEnv.ircTest.clients; + + // Create the channel + await bob.join(channel); + + const adminRoomId = await testEnv.createAdminRoomHelper(alice); + const cRoomId = await testEnv.createProvisionedRoom(alice, adminRoomId, channel, false); + + // And finally wait for bob to appear. + const bobUserId = `@irc_${bob.nick}:${homeserver.domain}`; + await alice.waitForRoomEvent( + {eventType: 'm.room.member', sender: bobUserId, stateKey: bobUserId, roomId: cRoomId} + ); + const aliceMsg = bob.waitForEvent('message', 10000); + alice.sendText(cRoomId, "Hello bob!"); + await aliceMsg; + + // Note, the bridge can't kick bannedUser due to lacking perms. + await bannedUser.joinRoom(cRoomId); + const connectionStateEv = alice.waitForRoomEvent({ + eventType: 'org.matrix.appservice-irc.connection', + sender: testEnv.ircBridge.appServiceUserId, + roomId: cRoomId + }); + await bob.say(channel, "Hi alice!"); + const connectionEventData = (await connectionStateEv).data; + expect(connectionEventData.content.blocked).toBe(true); + + await bannedUser.leaveRoom(cRoomId); + const bobMsg = alice.waitForRoomEvent( + {eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId} + ); + const connectionStateEvUnblocked = alice.waitForRoomEvent({ + eventType: 'org.matrix.appservice-irc.connection', + sender: testEnv.ircBridge.appServiceUserId, + roomId: cRoomId + }); + await bob.say(channel, "Hi alice!"); + + const connectionEventDataUnblocked = (await connectionStateEvUnblocked).data; + expect(connectionEventDataUnblocked.content.blocked).toBe(false); + await bobMsg; + }); +}); diff --git a/spec/util/e2e-test.ts b/spec/util/e2e-test.ts index a94fcbff5..53345a57c 100644 --- a/spec/util/e2e-test.ts +++ b/spec/util/e2e-test.ts @@ -3,7 +3,7 @@ import { BridgeConfig } from "../../src/config/BridgeConfig"; import { Client as PgClient } from "pg"; import { ComplementHomeServer, createHS, destroyHS } from "./homerunner"; import { IrcBridge } from '../../src/bridge/IrcBridge'; -import { IrcServer } from "../../src/irc/IrcServer"; +import { IrcServer, IrcServerConfig } from "../../src/irc/IrcServer"; import { MatrixClient } from "matrix-bot-sdk"; import { TestIrcServer } from "matrix-org-irc"; import { IrcConnectionPool } from "../../src/pool-service/IrcConnectionPool"; @@ -11,6 +11,7 @@ import { expect } from "@jest/globals"; import dns from 'node:dns'; import fs from "node:fs/promises"; import { WriteStream, createWriteStream } from "node:fs"; + // Needed to make tests work on GitHub actions. Node 17+ defaults // to IPv6, and the homerunner domain resolves to IPv6, but the // runtime doesn't actually support IPv6 🤦 @@ -27,6 +28,7 @@ interface Opts { ircNicks?: string[]; timeout?: number; config?: Partial, + ircServerConfig?: Partial, traceToFile?: boolean, } @@ -268,7 +270,8 @@ export class IrcBridgeE2ETest { initial: true, } } - } + }, + ...opts.ircServerConfig, } }, provisioning: { @@ -290,6 +293,9 @@ export class IrcBridgeE2ETest { debugApi: { enabled: false, port: 0, + }, + permissions: { + [homeserver.users[0].userId]: "admin", } }, ...config, @@ -392,4 +398,24 @@ export class IrcBridgeE2ETest { await client.joinRoom(cRoomId); return cRoomId; } + + public async createProvisionedRoom( + client: E2ETestMatrixClient, adminRoomId: string, channel: string, setPl = true + ): Promise { + const plumbedRoomId = await client.createRoom({ + name: `Plumbed room for ${channel}`, + preset: "public_chat", + invite: [this.ircBridge.appServiceUserId], + power_level_content_override: { + users: { + [this.ircBridge.appServiceUserId]: setPl ? 100 : 50, + [await client.getUserId()]: 100, + }, + kick: 75, + state_default: 50 + } + }); + await client.sendText(adminRoomId, `!plumb ${plumbedRoomId} localhost ${channel}`); + return plumbedRoomId; + } } From e99a25e8ff40e15147a3881f47f070b3eb26974f Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 4 Aug 2023 18:30:07 +0100 Subject: [PATCH 2/3] Add a test to ensure kicks work --- spec/e2e/privacyfilter.spec.ts | 38 +++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/spec/e2e/privacyfilter.spec.ts b/spec/e2e/privacyfilter.spec.ts index 7f50637c4..74b27595c 100644 --- a/spec/e2e/privacyfilter.spec.ts +++ b/spec/e2e/privacyfilter.spec.ts @@ -74,6 +74,42 @@ describe('Connection pooling', () => { await bobMsg; }); + it('should kick an excluded user if the bridge has permission', async () => { + const channel = `#${TestIrcServer.generateUniqueNick("test")}`; + + const { homeserver } = testEnv; + const alice = homeserver.users[0].client; + const bannedUser = homeserver.users[1].client; + const { bob } = testEnv.ircTest.clients; + + // Create the channel + await bob.join(channel); + + const adminRoomId = await testEnv.createAdminRoomHelper(alice); + const cRoomId = await testEnv.createProvisionedRoom(alice, adminRoomId, channel, true); + + // And finally wait for bob to appear. + const bobUserId = `@irc_${bob.nick}:${homeserver.domain}`; + await alice.waitForRoomEvent( + {eventType: 'm.room.member', sender: bobUserId, stateKey: bobUserId, roomId: cRoomId} + ); + const aliceMsg = bob.waitForEvent('message', 10000); + alice.sendText(cRoomId, "Hello bob!"); + await aliceMsg; + + // Note, the bridge can't kick bannedUser due to lacking perms. + const kick = alice.waitForRoomEvent({ + eventType: 'm.room.member', + sender: testEnv.ircBridge.appServiceUserId, + roomId: cRoomId, + stateKey: homeserver.users[1].userId, + }); + await bannedUser.joinRoom(cRoomId); + const kickContent = (await kick).data; + expect(kickContent.content.reason).toEqual('Test kick'); + expect(kickContent.content.membership).toEqual('leave'); + }); + it('should not bridge messages with an excluded user', async () => { const channel = `#${TestIrcServer.generateUniqueNick("test")}`; @@ -121,7 +157,7 @@ describe('Connection pooling', () => { expect(connectionEventData.content.blocked).toBe(true); }); - fit('should unblock a blocked channel if all excluded users leave', async () => { + it('should unblock a blocked channel if all excluded users leave', async () => { const channel = `#${TestIrcServer.generateUniqueNick("test")}`; const { homeserver } = testEnv; From 90e43cd079053be07005ab51fde1d172f4ca6f3e Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 4 Aug 2023 18:30:35 +0100 Subject: [PATCH 3/3] changelog --- changelog.d/1766.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1766.misc diff --git a/changelog.d/1766.misc b/changelog.d/1766.misc new file mode 100644 index 000000000..fe1f91ded --- /dev/null +++ b/changelog.d/1766.misc @@ -0,0 +1 @@ +Add a test for the privacy filter.