Update tests and instructions to intercept reports also with v3 endpoint (#388)

In both our instructions and our tests, we use the r0 endpoint to intercept abuse reports. This endpoint is deprecated and not implemented by all clients. This PR updates the instructions and tests to the new endpoint.
This commit is contained in:
David Teller 2022-10-18 15:48:39 +02:00 committed by GitHub
parent 938b9fea8f
commit 7b0edadd17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 214 additions and 206 deletions

View File

@ -48,7 +48,7 @@ This requires two configuration steps:
1. In your Mjölnir configuration file, typically `/etc/mjolnir/config/production.yaml`, copy and paste the `web` section from `default.yaml`, if you don't have it yet (it appears with version 1.20) and set `enabled: true` for both `web` and
`abuseReporting`.
2. Setup a reverse proxy that will redirect requests from `^/_matrix/client/r0/rooms/([^/]*)/report/(.*)$` to `http://host:port/api/1/report/$1/$2`, where `host` is the host where you run Mjölnir, and `port` is the port you configured in `production.yaml`. For an example nginx configuration, see `test/nginx.conf`. It's the confirmation we use during runtime testing.
2. Setup a reverse proxy that will redirect requests from `^/_matrix/client/(r0|v3)/rooms/([^/]*)/report/(.*)$` to `http://host:port/api/1/report/$2/$3`, where `host` is the host where you run Mjölnir, and `port` is the port you configured in `production.yaml`. For an example nginx configuration, see `test/nginx.conf`. It's the confirmation we use during runtime testing.
### Security note

View File

@ -1,6 +1,5 @@
import { strict as assert } from "assert";
import config from "../../src/config";
import { matrixClient } from "./mjolnirSetupUtils";
import { newTestUser } from "./clientHelper";
import { ReportManager, ABUSE_ACTION_CONFIRMATION_KEY, ABUSE_REPORT_KEY } from "../../src/report/ReportManager";
@ -20,12 +19,19 @@ const REPORT_NOTICE_REGEXPS = {
describe("Test: Reporting abuse", async () => {
it('Mjölnir intercepts abuse reports', async function() {
this.timeout(60000);
// Testing with successive versions of the API.
//
// As of this writing, v3 is the standard, while r0 is deprecated. However,
// both versions are still in use in the wild.
// Note that this version change only affects the actual URL at which reports
// are sent.
for (let endpoint of ['v3', 'r0']) {
it(`Mjölnir intercepts abuse reports with endpoint ${endpoint}`, async function() {
this.timeout(90000);
// Listen for any notices that show up.
let notices: any[] = [];
matrixClient()!.on("room.event", (roomId, event) => {
this.mjolnir.client.on("room.event", (roomId, event) => {
if (roomId = this.mjolnir.managementRoomId) {
notices.push(event);
}
@ -49,7 +55,6 @@ describe("Test: Reporting abuse", async () => {
let badText3 = `<b>BAD</b>: ${Math.random()}`; // Will be reported as abuse.
let badText4 = [...Array(1024)].map(_ => `${Math.random()}`).join(""); // Text is too long.
let badText5 = [...Array(1024)].map(_ => "ABC").join("\n"); // Text has too many lines.
let goodEventId = await goodUser.sendText(roomId, goodText);
let badEventId = await badUser.sendText(roomId, badText);
let badEventId2 = await badUser.sendText(roomId, badText2);
let badEventId3 = await badUser.sendText(roomId, badText3);
@ -58,11 +63,11 @@ describe("Test: Reporting abuse", async () => {
let badEvent2Comment = `COMMENT: ${Math.random()}`;
console.log("Test: Reporting abuse - send reports");
let reportsToFind = []
let reportsToFind: any[] = []
// Time to report, first without a comment, then with one.
try {
await goodUser.doRequest("POST", `/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/report/${encodeURIComponent(badEventId)}`);
await goodUser.doRequest("POST", `/_matrix/client/${endpoint}/rooms/${encodeURIComponent(roomId)}/report/${encodeURIComponent(badEventId)}`);
reportsToFind.push({
reporterId: goodUserId,
accusedId: badUserId,
@ -76,7 +81,7 @@ describe("Test: Reporting abuse", async () => {
}
try {
await goodUser.doRequest("POST", `/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/report/${encodeURIComponent(badEventId2)}`, "", {
await goodUser.doRequest("POST", `/_matrix/client/${endpoint}/rooms/${encodeURIComponent(roomId)}/report/${encodeURIComponent(badEventId2)}`, "", {
reason: badEvent2Comment
});
reportsToFind.push({
@ -92,7 +97,7 @@ describe("Test: Reporting abuse", async () => {
}
try {
await goodUser.doRequest("POST", `/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/report/${encodeURIComponent(badEventId3)}`, "");
await goodUser.doRequest("POST", `/_matrix/client/${endpoint}/rooms/${encodeURIComponent(roomId)}/report/${encodeURIComponent(badEventId3)}`, "");
reportsToFind.push({
reporterId: goodUserId,
accusedId: badUserId,
@ -106,7 +111,7 @@ describe("Test: Reporting abuse", async () => {
}
try {
await goodUser.doRequest("POST", `/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/report/${encodeURIComponent(badEventId4)}`, "");
await goodUser.doRequest("POST", `/_matrix/client/${endpoint}/rooms/${encodeURIComponent(roomId)}/report/${encodeURIComponent(badEventId4)}`, "");
reportsToFind.push({
reporterId: goodUserId,
accusedId: badUserId,
@ -121,7 +126,7 @@ describe("Test: Reporting abuse", async () => {
}
try {
await goodUser.doRequest("POST", `/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/report/${encodeURIComponent(badEventId5)}`, "");
await goodUser.doRequest("POST", `/_matrix/client/${endpoint}/rooms/${encodeURIComponent(roomId)}/report/${encodeURIComponent(badEventId5)}`, "");
reportsToFind.push({
reporterId: goodUserId,
accusedId: badUserId,
@ -137,7 +142,7 @@ describe("Test: Reporting abuse", async () => {
console.log("Test: Reporting abuse - wait");
await new Promise(resolve => setTimeout(resolve, 1000));
let found = [];
let found: any[] = [];
for (let toFind of reportsToFind) {
for (let event of notices) {
if ("content" in event && "body" in event.content) {
@ -147,7 +152,7 @@ describe("Test: Reporting abuse", async () => {
}
let report = event.content[ABUSE_REPORT_KEY];
let body = event.content.body as string;
let matches = new Map();
let matches: Map<string, RegExpMatchArray> | null = new Map();
for (let key of Object.keys(REPORT_NOTICE_REGEXPS)) {
let match = body.match(REPORT_NOTICE_REGEXPS[key]);
if (match) {
@ -168,26 +173,26 @@ describe("Test: Reporting abuse", async () => {
assert(body.length < 3000, `The report shouldn't be too long ${body.length}`);
assert(body.split("\n").length < 200, "The report shouldn't have too many newlines.");
assert.equal(matches.get("event")!.groups.eventId, toFind.eventId, "The report should specify the correct event id");;
assert.equal(matches.get("event")!.groups!.eventId, toFind.eventId, "The report should specify the correct event id");;
assert.equal(matches.get("reporter")!.groups.reporterId, toFind.reporterId, "The report should specify the correct reporter");
assert.equal(matches.get("reporter")!.groups!.reporterId, toFind.reporterId, "The report should specify the correct reporter");
assert.equal(report.reporter_id, toFind.reporterId, "The embedded report should specify the correct reporter");
assert.ok(toFind.reporterId.includes(matches.get("reporter")!.groups.reporterDisplay), "The report should display the correct reporter");
assert.ok(toFind.reporterId.includes(matches.get("reporter")!.groups!.reporterDisplay), "The report should display the correct reporter");
assert.equal(matches.get("accused")!.groups.accusedId, toFind.accusedId, "The report should specify the correct accused");
assert.equal(matches.get("accused")!.groups!.accusedId, toFind.accusedId, "The report should specify the correct accused");
assert.equal(report.accused_id, toFind.accusedId, "The embedded report should specify the correct accused");
assert.ok(toFind.accusedId.includes(matches.get("accused")!.groups.accusedDisplay), "The report should display the correct reporter");
assert.ok(toFind.accusedId.includes(matches.get("accused")!.groups!.accusedDisplay), "The report should display the correct reporter");
if (toFind.text) {
assert.equal(matches.get("content")!.groups.eventContent, toFind.text, "The report should contain the text we inserted in the event");
assert.equal(matches.get("content")!.groups!.eventContent, toFind.text, "The report should contain the text we inserted in the event");
}
if (toFind.textPrefix) {
assert.ok(matches.get("content")!.groups.eventContent.startsWith(toFind.textPrefix), `The report should contain a prefix of the long text we inserted in the event: ${toFind.textPrefix} in? ${matches.get("content")!.groups.eventContent}`);
assert.ok(matches.get("content")!.groups!.eventContent.startsWith(toFind.textPrefix), `The report should contain a prefix of the long text we inserted in the event: ${toFind.textPrefix} in? ${matches.get("content")!.groups!.eventContent}`);
}
if (toFind.comment) {
assert.equal(matches.get("comments")!.groups.comments, toFind.comment, "The report should contain the comment we added");
assert.equal(matches.get("comments")!.groups!.comments, toFind.comment, "The report should contain the comment we added");
}
assert.equal(matches.get("room")!.groups.roomAliasOrId, roomId, "The report should specify the correct room");
assert.equal(matches.get("room")!.groups!.roomAliasOrId, roomId, "The report should specify the correct room");
assert.equal(report.room_id, roomId, "The embedded report should specify the correct room");
found.push(toFind);
break;
@ -215,12 +220,13 @@ describe("Test: Reporting abuse", async () => {
}
}
});
}
it('The redact action works', async function() {
this.timeout(60000);
// Listen for any notices that show up.
let notices = [];
matrixClient().on("room.event", (roomId, event) => {
let notices: any[] = [];
this.mjolnir.client.on("room.event", (roomId, event) => {
if (roomId = this.mjolnir.managementRoomId) {
notices.push(event);
}
@ -228,7 +234,7 @@ describe("Test: Reporting abuse", async () => {
// Create a moderator.
let moderatorUser = await newTestUser(this.config.homeserverUrl, { name: { contains: "reporting-abuse-moderator-user" }});
matrixClient().inviteUser(await moderatorUser.getUserId(), this.mjolnir.managementRoomId);
this.mjolnir.client.inviteUser(await moderatorUser.getUserId(), this.mjolnir.managementRoomId);
await moderatorUser.joinRoom(this.mjolnir.managementRoomId);
// Create a few users and a room.
@ -244,8 +250,8 @@ describe("Test: Reporting abuse", async () => {
await goodUser.joinRoom(roomId);
// Setup Mjölnir as moderator for our room.
await moderatorUser.inviteUser(await matrixClient().getUserId(), roomId);
await moderatorUser.setUserPowerLevel(await matrixClient().getUserId(), roomId, 100);
await moderatorUser.inviteUser(await this.mjolnir.client.getUserId(), roomId);
await moderatorUser.setUserPowerLevel(await this.mjolnir.client.getUserId(), roomId, 100);
console.log("Test: Reporting abuse - send messages");
// Exchange a few messages.
@ -275,7 +281,7 @@ describe("Test: Reporting abuse", async () => {
console.log("Test: Reporting abuse - wait");
await new Promise(resolve => setTimeout(resolve, 1000));
let mjolnirRooms = new Set(await matrixClient().getJoinedRooms());
let mjolnirRooms = new Set(await this.mjolnir.client.getJoinedRooms());
assert.ok(mjolnirRooms.has(roomId), "Mjölnir should be a member of the room");
// Find the notice
@ -293,7 +299,7 @@ describe("Test: Reporting abuse", async () => {
assert.ok(noticeId, "We should have found our notice");
// Find the buttons.
let buttons = [];
let buttons: any[] = [];
for (let event of notices) {
if (event["type"] != "m.reaction") {
continue;
@ -347,7 +353,7 @@ describe("Test: Reporting abuse", async () => {
await new Promise(resolve => setTimeout(resolve, 1000));
// This should have redacted the message.
let newBadEvent = await matrixClient().getEvent(roomId, badEventId);
let newBadEvent = await this.mjolnir.client.getEvent(roomId, badEventId);
assert.deepEqual(Object.keys(newBadEvent.content), [], "Redaction should have removed the content of the offending event");
});
});

View File

@ -12,7 +12,7 @@ export const mochaHooks = {
console.error("---- entering test", JSON.stringify(this.currentTest.title)); // Makes MatrixClient error logs a bit easier to parse.
console.log("mochaHooks.beforeEach");
// Sometimes it takes a little longer to register users.
this.timeout(10000);
this.timeout(30000);
const config = this.config = configRead();
this.managementRoomAlias = config.managementRoom;
this.mjolnir = await makeMjolnir(config);

View File

@ -6,8 +6,10 @@ http {
server {
listen 8081;
location ~ ^/_matrix/client/r0/rooms/([^/]*)/report/(.*)$ {
location ~ ^/_matrix/client/(r0|v3)/rooms/([^/]*)/report/(.*)$ {
# Abuse reports should be sent to Mjölnir.
# The r0 endpoint is deprecated but still used by many clients.
# As of this writing, the v3 endpoint is the up-to-date version.
# Add CORS, otherwise a browser will refuse this request.
add_header 'Access-Control-Allow-Origin' '*' always; # Note: '*' is for testing purposes. For your own server, you probably want to tighten this.
@ -18,8 +20,8 @@ http {
add_header 'Access-Control-Max-Age' 1728000; # cache preflight value for 20 days
# Alias the regexps, to ensure that they're not rewritten.
set $room_id $1;
set $event_id $2;
set $room_id $2;
set $event_id $3;
proxy_pass http://127.0.0.1:8082/api/1/report/$room_id/$event_id;
}
location / {