mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 01:36:06 -04:00
Add test for ruleserver policy consumption.
Ensures that the consumer of the ruleserver rules is enforcing them.
This commit is contained in:
parent
7ee3ce1dd2
commit
4537cf0d8a
@ -1,4 +1,52 @@
|
|||||||
import { MatrixClient } from "matrix-bot-sdk";
|
import { MatrixClient } from "matrix-bot-sdk";
|
||||||
|
import { strict as assert } from "assert";
|
||||||
|
import * as crypto from "crypto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise that resolves to the first event replying to the event produced by targetEventThunk.
|
||||||
|
* @param client A MatrixClient that is already in the targetRoom. We will use it to listen for the event produced by targetEventThunk.
|
||||||
|
* This function assumes that the start() has already been called on the client.
|
||||||
|
* @param targetRoom The room to listen for the reply in.
|
||||||
|
* @param targetEventThunk A function that produces an event ID when called. This event ID is then used to listen for a reply.
|
||||||
|
* @returns The replying event.
|
||||||
|
*/
|
||||||
|
export async function getFirstReply(client: MatrixClient, targetRoom: string, targetEventThunk: () => Promise<string>): Promise<any> {
|
||||||
|
let reactionEvents = [];
|
||||||
|
const addEvent = function (roomId, event) {
|
||||||
|
if (roomId !== targetRoom) return;
|
||||||
|
if (event.type !== 'm.room.message') return;
|
||||||
|
reactionEvents.push(event);
|
||||||
|
};
|
||||||
|
let targetCb;
|
||||||
|
try {
|
||||||
|
client.on('room.event', addEvent)
|
||||||
|
const targetEventId = await targetEventThunk();
|
||||||
|
for (let event of reactionEvents) {
|
||||||
|
const in_reply_to = event.content['m.relates_to']?.['m.in_reply_to'];
|
||||||
|
if (in_reply_to.event_id === targetEventId) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await new Promise(resolve => {
|
||||||
|
targetCb = function(roomId, event) {
|
||||||
|
if (roomId !== targetRoom) return;
|
||||||
|
if (event.type !== 'm.room.message') return;
|
||||||
|
const in_reply_to = event.content['m.relates_to']?.['m.in_reply_to'];
|
||||||
|
if (in_reply_to?.event_id === targetEventId) {
|
||||||
|
resolve(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.on('room.event', targetCb);
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
client.removeListener('room.event', addEvent);
|
||||||
|
if (targetCb) {
|
||||||
|
client.removeListener('room.event', targetCb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise that resolves to an event that is reacting to the event produced by targetEventThunk.
|
* Returns a promise that resolves to an event that is reacting to the event produced by targetEventThunk.
|
||||||
@ -44,3 +92,19 @@ export async function getFirstReaction(client: MatrixClient, targetRoom: string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new banlist for mjolnir to watch and return the shortcode that can be used to refer to the list in future commands.
|
||||||
|
* @param managementRoom The room to send the create command to.
|
||||||
|
* @param mjolnir A syncing matrix client.
|
||||||
|
* @param client A client that isn't mjolnir to send the message with, as you will be invited to the room.
|
||||||
|
* @returns The shortcode for the list that can be used to refer to the list in future commands.
|
||||||
|
*/
|
||||||
|
export async function createBanList(managementRoom: string, mjolnir: MatrixClient, client: MatrixClient): Promise<string> {
|
||||||
|
const listName = crypto.randomUUID();
|
||||||
|
const listCreationResponse = await getFirstReply(mjolnir, managementRoom, async () => {
|
||||||
|
return await client.sendMessage(managementRoom, { msgtype: 'm.text', body: `!mjolnir list create ${listName} ${listName}`});
|
||||||
|
});
|
||||||
|
assert.equal(listCreationResponse.content.body.includes('This list is now being watched.'), true, 'could not create a list to test with.');
|
||||||
|
return listName;
|
||||||
|
}
|
||||||
|
143
test/integration/policyConsumptionTest.ts
Normal file
143
test/integration/policyConsumptionTest.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { strict as assert } from "assert";
|
||||||
|
|
||||||
|
import { newTestUser } from "./clientHelper";
|
||||||
|
import { getMessagesByUserIn } from "../../src/utils";
|
||||||
|
import config from "../../src/config";
|
||||||
|
import axios from "axios";
|
||||||
|
import { LogService } from "matrix-bot-sdk";
|
||||||
|
import { createBanList, getFirstReaction } from "./commands/commandUtils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a copy of the rules from the ruleserver.
|
||||||
|
*/
|
||||||
|
async function currentRules() {
|
||||||
|
return await (await axios.get(`http://${config.web.address}:${config.web.port}/api/1/ruleserver/updates/`)).data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for the rules to change as a result of the thunk. The returned promise will resolve when the rules being served have changed.
|
||||||
|
* @param thunk Should cause the rules the RuleServer is serving to change some way.
|
||||||
|
*/
|
||||||
|
async function waitForRuleChange(thunk): Promise<void> {
|
||||||
|
const initialRules = await currentRules();
|
||||||
|
let rules = initialRules;
|
||||||
|
// We use JSON.stringify like this so that it is pretty printed in the log and human readable.
|
||||||
|
LogService.debug('policyConsumptionTest', `Rules before we wait for them to change: ${JSON.stringify(rules, null, 2)}`);
|
||||||
|
await thunk();
|
||||||
|
while (rules.since === initialRules.since) {
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
setTimeout(resolve, 500);
|
||||||
|
})
|
||||||
|
rules = await currentRules();
|
||||||
|
};
|
||||||
|
// The problem is, we have no idea how long a consumer will take to process the changed rules.
|
||||||
|
// We know the pull peroid is 1 second though.
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
setTimeout(resolve, 1500);
|
||||||
|
})
|
||||||
|
LogService.debug('policyConsumptionTest', `Rules after they have changed: ${JSON.stringify(rules, null, 2)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Test: that policy lists are consumed by the associated synapse module", function () {
|
||||||
|
this.afterEach(async function () {
|
||||||
|
this.timeout(5000)
|
||||||
|
LogService.debug('policyConsumptionTest', `Rules at end of test ${JSON.stringify(await currentRules(), null, 2)}`);
|
||||||
|
const mjolnir = config.RUNTIME.client!;
|
||||||
|
// Clear any state associated with the account.
|
||||||
|
await mjolnir.setAccountData('org.matrix.mjolnir.watched_lists', {
|
||||||
|
references: [],
|
||||||
|
});
|
||||||
|
})
|
||||||
|
this.beforeEach(async function () {
|
||||||
|
this.timeout(1000);
|
||||||
|
const mjolnir = config.RUNTIME.client!;
|
||||||
|
})
|
||||||
|
it('blocks users in antispam when they are banned from sending messages and invites serverwide.', async function() {
|
||||||
|
this.timeout(20000);
|
||||||
|
// Create a few users and a room.
|
||||||
|
let badUser = await newTestUser(false, "spammer");
|
||||||
|
let badUserId = await badUser.getUserId();
|
||||||
|
const mjolnir = config.RUNTIME.client!
|
||||||
|
let mjolnirUserId = await mjolnir.getUserId();
|
||||||
|
let moderator = await newTestUser(false, "moderator");
|
||||||
|
this.moderator = moderator;
|
||||||
|
await moderator.joinRoom(this.mjolnir.managementRoomId);
|
||||||
|
let unprotectedRoom = await badUser.createRoom({ invite: [await moderator.getUserId()]});
|
||||||
|
// We do this so the moderator can send invites, no other reason.
|
||||||
|
await badUser.setUserPowerLevel(await moderator.getUserId(), unprotectedRoom, 100);
|
||||||
|
await moderator.joinRoom(unprotectedRoom);
|
||||||
|
const banList = await createBanList(this.mjolnir.managementRoomId, mjolnir, moderator);
|
||||||
|
await badUser.sendMessage(unprotectedRoom, {msgtype: 'm.text', body: 'Something bad and mean'});
|
||||||
|
|
||||||
|
await waitForRuleChange(async () => {
|
||||||
|
await getFirstReaction(mjolnir, this.mjolnir.managementRoomId, '✅', async () => {
|
||||||
|
return await moderator.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir ban ${banList} ${badUserId}` });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await assert.rejects(badUser.sendMessage(unprotectedRoom, { msgtype: 'm.text', body: 'test'}), 'The bad user should be banned and unable to send messages.');
|
||||||
|
await assert.rejects(badUser.inviteUser(mjolnirUserId, unprotectedRoom), 'They should also be unable to send invitations.');
|
||||||
|
assert.ok(await moderator.inviteUser('@test:localhost:9999', unprotectedRoom), 'The moderator is not banned though so should still be able to invite');
|
||||||
|
assert.ok(await moderator.sendMessage(unprotectedRoom, { msgtype: 'm.text', body: 'test'}), 'They should be able to send messages still too.');
|
||||||
|
|
||||||
|
// Test we can remove the rules.
|
||||||
|
await waitForRuleChange(async () => {
|
||||||
|
await getFirstReaction(mjolnir, this.mjolnir.managementRoomId, '✅', async () => {
|
||||||
|
return await moderator.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir unban ${banList} ${badUserId}` });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
assert.ok(await badUser.sendMessage(unprotectedRoom, { msgtype: 'm.text', body: 'test'}));
|
||||||
|
assert.ok(await badUser.inviteUser(mjolnirUserId, unprotectedRoom));
|
||||||
|
})
|
||||||
|
it('Test: Cannot send message to a room that is listed in a policy list and cannot invite a user to the room either', async function () {
|
||||||
|
this.timeout(20000);
|
||||||
|
let badUser = await newTestUser(false, "spammer");
|
||||||
|
const mjolnir = config.RUNTIME.client!
|
||||||
|
let moderator = await newTestUser(false, "moderator");
|
||||||
|
await moderator.joinRoom(this.mjolnir.managementRoomId);
|
||||||
|
const banList = await createBanList(this.mjolnir.managementRoomId, mjolnir, moderator);
|
||||||
|
let badRoom = await badUser.createRoom();
|
||||||
|
let unrelatedRoom = await badUser.createRoom();
|
||||||
|
await badUser.sendMessage(badRoom, {msgtype: 'm.text', body: "Very Bad Stuff in this room"});
|
||||||
|
await waitForRuleChange(async () => {
|
||||||
|
await getFirstReaction(mjolnir, this.mjolnir.managementRoomId, '✅', async () => {
|
||||||
|
return await moderator.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir ban ${banList} ${badRoom}` });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await assert.rejects(badUser.sendMessage(badRoom, { msgtype: 'm.text', body: 'test'}), 'should not be able to send messagea to a room which is listed.');
|
||||||
|
await assert.rejects(badUser.inviteUser(await moderator.getUserId(), badRoom), 'should not be able to invite people to a listed room.');
|
||||||
|
assert.ok(await badUser.sendMessage(unrelatedRoom, { msgtype: 'm.text.', body: 'hey'}), 'should be able to send messages to unrelated room');
|
||||||
|
assert.ok(await badUser.inviteUser(await moderator.getUserId(), unrelatedRoom), 'They should still be able to invite to other rooms though');
|
||||||
|
// Test we can remove these rules.
|
||||||
|
await waitForRuleChange(async () => {
|
||||||
|
await getFirstReaction(mjolnir, this.mjolnir.managementRoomId, '✅', async () => {
|
||||||
|
return await moderator.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir unban ${banList} ${badRoom}` });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(await badUser.sendMessage(badRoom, { msgtype: 'm.text', body: 'test'}), 'should now be able to send messages to the room.');
|
||||||
|
assert.ok(await badUser.inviteUser(await moderator.getUserId(), badRoom), 'should now be able to send messages to the room.');
|
||||||
|
})
|
||||||
|
it('Test: When a list becomes unwatched, the associated policies are stopped.', async function () {
|
||||||
|
this.timeout(20000);
|
||||||
|
const mjolnir = config.RUNTIME.client!
|
||||||
|
let moderator = await newTestUser(false, "moderator");
|
||||||
|
await moderator.joinRoom(this.mjolnir.managementRoomId);
|
||||||
|
const banList = await createBanList(this.mjolnir.managementRoomId, mjolnir, moderator);
|
||||||
|
let targetRoom = await moderator.createRoom();
|
||||||
|
await moderator.sendMessage(targetRoom, {msgtype: 'm.text', body: "Fluffy Foxes."});
|
||||||
|
await waitForRuleChange(async () => {
|
||||||
|
await getFirstReaction(mjolnir, this.mjolnir.managementRoomId, '✅', async () => {
|
||||||
|
return await moderator.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir ban ${banList} ${targetRoom}` });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await assert.rejects(moderator.sendMessage(targetRoom, { msgtype: 'm.text', body: 'test'}), 'should not be able to send messages to a room which is listed.');
|
||||||
|
|
||||||
|
await waitForRuleChange(async () => {
|
||||||
|
await getFirstReaction(mjolnir, this.mjolnir.managementRoomId, '✅', async () => {
|
||||||
|
return await moderator.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir unwatch #${banList}:localhost:9999` });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(await moderator.sendMessage(targetRoom, { msgtype: 'm.text', body: 'test'}), 'should now be able to send messages to the room.');
|
||||||
|
})
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user